@IT·互联网程序员

Java堆外内存的回收机制

2018-09-21  本文已影响52人  薛定谔的猫Plus

1 堆外内存

JVM启动时分配的内存,称为堆内存,与之相对的,在代码中还可以使用堆外内存,不如Netty,广泛使用了堆外内存,但是这部分内存不归JVM管理,GC算法并不会对它们进行回收,所以使用堆外内存是需要格外小心,以防出现内存泄露。

2 堆外内存的申请和释放

JDK中使用DirectByteBuffer对象来表示堆外内存,可以通过-XX:MaxDirectMemorySize来指定最大的堆外内存,每个DirectByteBuffer对象在初始化时,都会创建一个对应的Cleaner对象,在Cleaner对象回收的时候回收这部分堆外内存。初始化时引用关系如下:


image

其中first是Cleaner类的静态变量,Cleaner对象在初始化时会被添加到Clener链表中,和first形成引用关系,ReferenceQueue是用来保存需要回收的Cleaner对象。

3 Cleaner如何与GC相关联

JDK除了StrongReference、SoftReference和WeakReference之外,还有一种PhantomReference是虚引用,Cleaner就是PhantomReference的子类。(针对这几种引用,后续专题讲解)

当GC时发现它除了PhantomReference外已不可达(持有它的DirectByteBuffer失效了),就会把它放进 Reference类pending list静态变量里。然后另有一条ReferenceHandler线程,名字叫 "Reference Handler"的,关注着这个pending list,如果看到有对象类型是Cleaner,就会执行它的clean(),在最终的处理里会通过Unsafe的free接口来释放DirectByteBuffer对应的堆外内存块。

4 堆外内存基于GC的回收

快速回顾一下堆内的GC机制,当新生代满了,就会发生young gc;如果此时对象还没失效,就不会被回收;撑过几次young gc后,对象被迁移到老生代;当老生代也满了,就会发生full gc。

这里可以看到一种尴尬的情况,因为DirectByteBuffer本身的个头很小,只要熬过了young gc,即使已经失效了也能在老生代里舒服的呆着,不容易把老生代撑爆触发full gc,如果没有别的大块头进入老生代触发full gc,就一直在那耗着,占着一大片堆外内存不释放。

    其实在初始化DirectByteBuffer对象时,如果当前堆外内存的条件很苛刻时,会主动调用System.gc()强制执行FGC。

这时,就只能靠触发system.gc()来救场了。如果还是无法释放,就可能会出现OOM。

  不过很多线上环境的JVM参数有-XX:+DisableExplicitGC,导致了System.gc()等于一个空函数,根本不会触发FGC,这点需要特别关注。
上一篇 下一篇

猜你喜欢

热点阅读