直接内存在面试中被问到还是挺多的。记得有次被问直接内存如何回收的?当时还有所准备,就回答 直接内存 属于堆外内存,不由gc直接回收,但是gc操作会触发回收操作。实际回收是由Cleaner完成,而Cleaner继承于幻引用(PhantomReference)。当时被问 由于幻引用不影响垃圾回收,有没可能Cleaner把直接内存回收了,但是直接内存的引用还有效呢,这不就有问题了么?当时心想还真是…
显然不会出现这种情况:直接内存引用还有效的情况下,堆外内存被Cleaner偷偷的回收了。当时没能回答上来一方面是自己掌握的不够扎实,另一方面就是面试官混淆了概念把自己绕进去了。
我们说幻引用指向不会影响对象的被回收,这个对象是指referent,而不是reference。
一个简单示意:
Object referent = new Object();
PhantomReference reference= new PhantomReference(referent, null);
即:referent 什么时候被回收,跟reference 的引用没有关系。
但referent 被回收收后,reference 是可以做一些事情的。这也是直接内存回收的大致逻辑。
DirectByDireteBuffer里初始化cleaner:
cleaner = Cleaner.create(this, new Deallocator(base, size, cap))
this 是DirectByDireteBuffer。Deallocator 实现了Runnable ,run方法里面才是真正进行回收的地方。
所以Cleaner不会在DirectByDireteBuffer 还有效的情况下提前 进行回收操作。只有DirectByDireteBuffer被判定为可回收之后才会触发 直接内存的回收操作。对于Cleaner具体细节可以搜索相关文档。
测试代码
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024 * 1024 * 256);
byte[] data = new byte[1024 * 1024 * 256];
Arrays.fill(data, (byte)1);
byteBuffer.put(data);
byteBuffer = null;
System.gc();
Thread.sleep(1000*600);
回收时调用栈
回收前后进程内存占用情况
第一列数差值两百多M左右,跟实际分配的内存大小差不多。