Skip to content

Latest commit

 

History

History
37 lines (22 loc) · 3.84 KB

堆外内存原理.md

File metadata and controls

37 lines (22 loc) · 3.84 KB

image

堆外内存定义

创建Java.nio.DirectByteBuffer时分配的内存

堆外内存优缺点
  • 优点: 提升了IO效率(减少了从堆内到堆外的数据拷贝);减小了GC压力(不参与GC,有特定的回收机制);
  • 缺点: 分配和回收堆外内存比较耗时;(解决方案:通过对象池避免频繁地创建和销毁堆外内存)

为什么堆外内存能够提升IO效率?

因为从堆内向磁盘/网卡读写数据时,数据会被先复制到堆外内存,然后堆外内存的数据被拷贝到硬件,如下图所示,直接写入堆外内存可避免堆内到堆外的一次数据拷贝

image

image

堆内拷贝到堆外的原因

操作系统把内存中的数据写入磁盘或网络时,要求数据所在的内存区域不能变动,但是JVM的GC机制会对内存进行整理,导致数据内存地址发生变化,所以无奈,JDK只能先拷贝到堆外内存(不受GC影响),然后把这个地址发给操作系统。

堆外内存回收

JDK中使用DirectByteBuffer对象来表示堆外内存,每个DirectByteBuffer对象在初始化时,都会创建一个对应的Cleaner对象,用于保存堆外内存的元信息(开始地址、大小和容量等),当DirectByteBuffer被GC回收后,Cleaner对象被放入ReferenceQueue中,然后由ReferenceHandler守护线程调用unsafe.freeMemory(address),回收堆外内存。

image

主动回收(推荐): 对于Sun的JDK,只要从DirectByteBuffer里取出那个sun.misc.Cleaner,然后调用它的clean()就行;

基于 GC 回收: 堆内的DirectByteBuffer对象被GC时,会调用cleaner回收其引用的堆外内存。问题是YGC只会将新生代里的不可达的DirectByteBuffer对象及其堆外内存回收,如果有大量的DirectByteBuffer对象移到了old区,但是又一直没有做CMS GC或者FGC,而只进行YGC,物理内存会被慢慢耗光,触发OOM; image

堆外内存使用注意

DirectByteBuffer对象在创建过程中会先通过Unsafe接口直接通过os::malloc来分配内存,然后将内存的起始地址和大小存到java.nio.DirectByteBuffer对象里,这样就可以直接操作这些内存。这些内存只有在DirectByteBuffer回收掉之后才有机会被回收,因此如果这些对象大部分都移到了old,但是一直没有触发CMS GC或者Full GC,那么悲剧将会发生,因为你的物理内存被他们耗尽了,因此为了避免这种悲剧的发生,通过-XX:MaxDirectMemorySize来指定最大的堆外内存大小,当使用达到了阈值的时候将调用System.gc来做一次full gc,以此来回收掉没有被使用的堆外内存。

为什么Cleaner对象能够被放入ReferenceQueue中?

Cleaner对象关联了一个PhantomReference引用,如果GC过程中某个对象除了只有PhantomReference引用它之外,并没有其他地方引用它了,那将会把这个引用放到java.lang.ref.Reference.pending队列里,在GC完毕的时候通知ReferenceHandler这个守护线程去执行一些后置处理,在最终的处理里会通过Unsafe的free接口来释放DirectByteBuffer对应的堆外内存块。