大白话系列深入浅出Cleaner+虚引用完成堆外内存的回收
Posted Roninaxious
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了大白话系列深入浅出Cleaner+虚引用完成堆外内存的回收相关的知识,希望对你有一定的参考价值。
在NIO技术中,使用allocateDirect()方法可以创建直接内存;如何释放该内存呢?
(1)通过手动释放内存(Cleaner+虚引用)
(2)交给JVM进行处理(Full GC)
1.直接内存的创建与销毁
base = UNSAFE.allocateMemory(size);
在DirectByteBuffer类构造方法中,主要通过allocateMemory方法完成堆外内存的创建,这个方法是Unsafe类中,这个类是干什么的呢?通过名字我们可以也可以看出这个类是不安全的,也就是它是能够直接操作堆外内存,超出了JVM的管辖范围!
UNSAFE.freeMemory(address);
需要注意的是通过Unsafe开辟的直接内存,需要通过调用freeMemory手动回收(当然帮你写了)
2.通过Cleaner+虚引用完成堆外内存回收
Java对象有四种引用方式:强软弱虚
虚引用PhantomReference一般来说极少使用,而且它不能单独使用,它需要和引用队列 ReferenceQueue一块使用
首先分析allocateDirect()方法底层(DirectByteBuffer类)
通过它的构造方法可以看出创建了Cleaner对象和Deallocator对象,首先分析Cleaner对象,通过看它的源码可以得出,它的底层维护了一个双向链表,当Cleaner对象初始化时,就会加入到这个Cleaner双向链表中(而且是安全的,使用了Synchronized,Cleaner类继承了虚引用)
public static Cleaner create(Object ob, Runnable thunk)
if (thunk == null)
return null;
return add(new Cleaner(ob, thunk));
当DirectByteBuffer对象不存在时,Cleaner对象不再处于引用链中,等到下一次GC时,Cleaner会被加入到ReferenceQueue队列中,并执行clean方法;将将自身从Cleaner双向链表中移除(remove),然利用多态调用了Deallocator类中的run方法释放内存
public void clean()
if (!remove(this))
return;
try
thunk.run();
catch (final Throwable x)
AccessController.doPrivileged(new PrivilegedAction<>()
public Void run()
if (System.err != null)
new Error("Cleaner terminated abnormally", x)
.printStackTrace();
System.exit(1);
return null;
);
public void run()
if (address == 0)
// Paranoia
return;
UNSAFE.freeMemory(address);
address = 0;
Bits.unreserveMemory(size, capacity);
3.总结创建与销毁流程
分析下图总结流程(来源百度图片)
初始时,创建了一个DirectByteBuffer对象,在DirectByteBuffer的构造函数中创建了一个Cleaner对象和Deallocator对象;Cleaner对象初始化时将自身加入到了链表中;一旦DirectByteBuffer对象被回收,那么下次GC时Cleaner(继承了虚引用)对象会被放到ReferenceQueue队列中并执行clean方法(多态调用了Deallocator中的run方法释放内存)
4.如何一步步顺序解读源码流程
(1)ByteBuffer抽象类的allocateDirect方法
(2)DirectByteBfufer的构造方法
(3)Cleaner类的create方法
(4)Cleaner类的add方法
(5)回到第二步
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
(6)Deallocator类的构造方法
将Cleaner类中分配的直接内存的地址、大小等进行复制,因为这个类的run方法是进行内存回收的根方法。
5.使用直接内存的利弊分析
使用直接内存肯定也有坏处,否者还有堆内存什么事;使用直接内存不受JVM的管辖,稍有不慎容易造成内存泄漏!当然使用直接内存减少了GC的时间(交给了操作系统进行管理),另一方面与磁盘交互时使用直接内存减少了复制操作,效率得到提高。
以上是关于大白话系列深入浅出Cleaner+虚引用完成堆外内存的回收的主要内容,如果未能解决你的问题,请参考以下文章
使用sun.misc.Cleaner或者PhantomReference实现堆外内存的自动释放
使用sun.misc.Cleaner或者PhantomReference实现堆外内存的自动释放