java中jvm中非堆内存垃圾回收相关说明??

参考:https://www.jianshu.com/p/4004d36b64a4

jvm的垃圾回收机制大家应该已经很熟了,jvm主要是回收堆内存,而我我们在开发中会遇到在堆外分配内存的情况,那这部分内存是怎么回收的呢?

java中的堆外内存一般指DirectByteBuffer,他在高性能通信框架netty,mina中使用频繁,通常用来作为缓冲区。我们来看DirectByteBuffer的实现,如下图所示:


       可以发现底层是通过Unsafe类的allocateMemory方法,unsafe类通过调用c++的方法来进行内存的分配,我们知道c++的内存可以通过析构方法来进行释放,然而java是通过JVM进行垃圾回收的,而JVM不会处理堆外内存,那java是如何回收Unsafe分配的堆外内存呢?

       这里额外提一下Unsafe类,JDk是不推荐大家使用他的,因为直接操作底层系统,使程序脱离java的控制,所以他会被命名为"不安全的"。

       通过构造函数可知,每创建一个DirectByteBuffer的同时都会创建一个Cleaner对象,Cleaner对象继承了PhantomReference即幽灵引用。关于java中的引用后面再写。PhantomReference主要是用来跟踪对象何时被回收的,它不能影响gc决策,但是gc过程中如果发现某个对象除了只有PhantomReference引用它之外,并没有其他的地方引用它了,那将会把这个引用放到java.lang.ref.Reference.pending队列里,在gc完毕的时候通知ReferenceHandler这个守护线程去执行一些后置处理,而DirectByteBuffer关联的PhantomReference是PhantomReference的一个子类,在最终的处理里会通过Unsafe的free接口来释放DirectByteBuffer对应的堆外内存块。


这里再摘抄一段美团点评技术博客Java魔法类:Unsafe应用解析中的解释:Cleaner继承自Java四大引用类型之一的虚引用PhantomReference(众所周知,无法通过虚引用获取与之关联的对象实例,且当对象仅被虚引用引用时,在任何发生GC的时候,其均可被回收),通常PhantomReference与引用队列ReferenceQueue结合使用,可以实现虚引用关联对象被垃圾回收时能够进行系统通知、资源清理等功能。如下图所示,当某个被Cleaner引用的对象将被回收时,JVM垃圾收集器会将此对象的引用放入到对象引用中的pending链表中,等待Reference-Handler进行相关处理。其中,Reference-Handler为一个拥有最高优先级的守护线程,会循环不断的处理pending链表中的对象引用,执行Cleaner的clean方法进行相关清理工作。所以当DirectByteBuffer仅被Cleaner引用(即为虚引用)时,其可以在任意GC时段被回收。当DirectByteBuffer实例对象被回收时,在Reference-Handler线程操作中,会调用Cleaner的clean方法根据创建Cleaner时传入的Deallocator来进行堆外内存的释放。

如下图所示:


在分配内存前,会调用Bits.reserveMemory方法,该方法中判断是否有足够内存进行分配,该内存大小可以通过-XX:MaxDirectMemorySize参数指定,或者默认值为“新生代的最大值-一个survivor的大小+老生代的最大值”。若没有足够内存时,会调用System.gc触发full gc,即主动的再触发一遍ReferenceHandler的后置处理。

这里特别说一下一个很多人都会遇到的坑(占小狼一篇文章就有提到):我们一般在jvm参数中会使用-XX:+DisableExplicitGC将System.gc关闭,主要为了防止程序员乱用导致系统发生不必要的gc从而影响性能,然而在使用了大量堆外内存的场景,比如使用netty框架开发时,这个参数是不能禁用的,因为其使用了大量的DirectByteBuffer。DirectByteBuffer对象关联了堆外内存,这些内存只有在DirectByteBuffer回收掉之后才有机会被回收,因此如果这些对象大部分都移到了old,但是一直没有触发CMS GC或者Full GC,那么物理内存将被耗尽,同时也要尽量使用-XX:MaxDirectMemorySize来指定最大的堆外内存大小

??

??






评论

© java小牛钱小白 | Powered by LOFTER