JVM堆外内存回收原理
今天有一个刷新我三观的一个观念,开拓了我的思维,批判了我的脑回路.
今天我说天龙里面觉得萧峰为什么出门自带BGM,为什么那么叼.他说萧帮主算啥,最喜欢段誉,连妹妹都不放过.我说好歹么啥血缘关系吧,毕竟段誉是段延庆的儿子嘛,隔开三代了.他说好歹还是有关系的,毕竟都是人族嘛….我一口咖啡喷显示器上.
说一下JVM堆外内存的回收原理吧.Java有堆内内存和堆外内存,堆内内存有GC,堆外内存的话GC就爱莫能助了毕竟不在自己的地盘上.
堆外内存想比较堆内内存的优势就在IO操作上,能够节省堆内内存到堆外内存的复制操作,性能更高.这在netty中有比较好的体现(参考netty源码).而且磁盘IO操作可以使用内存映射,性能比较高,而且不用受制于堆内内存的GC.
来 看看DirectByteBuffer的构造函数
Bits.reserveMemory(size , cap);
这个方法向Bits类申请内存额度,Bits类内部维护了目前已经使用的堆外内存的值,会查验(Check)当前申请大小(默认大小==堆内存大小),可以使用-XX:MaxDirectMemorySize来设置.
如果查验Check不通过,会主动执行System.gc(),然后100毫秒后会再次Check,如果还是内存不足,就会OOM.
如果Check通过调用unsafe.allocateMemory(size)来分配内存,返回内存地址,然后内存清空.
因为申请内存之前会有可能调用GC,所以在设置的时候需要小心一些选项,比如-XX:+DisableExplicitGC,这个参数是禁止代码中显式调用GC.
clear = Clearner.create(this , new Deallocator(base , size , cap));
这是用来回收堆外内存的,工作机制需要看一下Clearner类源码.
Clearner维护了一个Clearner对象的链表,通过create(Object,Runnable)创建Clearner对象,调用自身的add方法,将其加入到链表中.Clearner的clean方法将其对象自身从链表中删除,确保只能调用一次,然后执行this.thunk的run方法(thunk是由创建时传入的Runnable参数,clean只是除法Runnable的run方法,具体执行什么并不关心)
Deallocator类是DirectByteBuffer的静态内部类,Deallocator类的对象就是DirectByteBuffer传入的Runnable参数类
其中run()中的unsafe.freeMemory(address);见名知意,就是释放内存.然后Bits.unreserveMemory(size, capacity);对Bits里使用的内存数据进行更新.
内存释放和回收有自动回收和手动回收两种.
自动回收,通过GC回收内存.GC在工作时会扫描DirectByteBuffer对象是否有影响到GC的引用,如果没有则回收DirectByteBuffer对象的同时回收其占用的堆外内存.但是堆外内存的回收并不归GC负责.因为Clearner继承了PhantomReference(虚引用,不影响JVM是否回收某个对象的判断,当GC某个对象的时候如果这个对象存在虚引用,会将这个对C加入ReferenceQueue),而PhantomReference又继承自Reference类,其中run方法
此方法中有一个死循环,synchronized中接收JVM传递过来的reference(这里的pending),而后调用了Clearner的clean(),而这个reference并没有被放入队列中,所以Clearner的许引用不放入ReferenceQueue.
手动回收,直接调用DirectByteBuffer的cleaner的clean方法来释放内存.因为Clearner类是private的,所以需要通过反射的方式来调用.因为DirectByteBuffer实现DirectBuffer接口,接口中有clearner方法可以获取到clearner对象,然后自己慢慢想咯 →_→.