DirectByteBuffer直接内存(堆外内存)是怎么被回收的?
DirectByteBuffer,即指向堆外内存,如果要释放它的内存,是不能通过 Jvm 垃圾回收器来回收(垃圾回收器只能回收堆内内存),需要通过虚引用的方式。
一直以来,都只是知道是通过虚引用,但是到底具体怎么回收的,不得而知。最近听黄老师课并翻了代码,才终于清晰。
以下是个人总结。
在 DirectByteBuffer 类文件中,可以看到其内部有 Deallocator 和 Cleaner 两个类的声明,而这两个类就是跟内存的回收有重要的关系。
堆外内存仍然属于用户空间并且属于 JVM 进程的一部分。
一、Deallocator 和 Cleaner
1、Deallocator
第一个重要的类,Deallocator。
Deallocator 是一个 Runnable,那么自然就关注它的 run 方法。在 run 方法里面,调用了 unsafe.freeMemory 方法来达到释放内存的目的。
因为分配内存用的是 unsafe.setMemory,那么对应的释放也会是调用 unsafe 类的方法。

那么接下来的问题就是 Deallocator 是被谁调用来开启 run 方法的。
那答案就是 Cleaner 了。
2、Cleaner
从下面的有注释的代码可以看出,
(1) Cleaner 继承自 PhantomReference 虚引用;
(2) 持有一个 Runnable 对象,在 clean() 方法中会调用该 Runnable 的 run() 方法。
刚刚上面的 Deallocator 这个类正好就是个 Runnable。
没错,Cleaner 中的 Runnable thunk 指向Deallocator 。在 new DirectByteBuffer 时调用的构造方法中,就会创建 cleaner 对象。

所以在调用 Cleaner.clean() 时会调用 Deallocator的run()方法,从而回收堆外内存。
那么接下来问题就变成 Cleaner的clean方法又是什么时候被调用的?
//(只摘录了部分代码)
public class Cleaner extends PhantomReference<Object> { // !!! 继承 虚引用
private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue();
private static Cleaner first = null;
private Cleaner next = null;
private Cleaner prev = null;
private final Runnable thunk; //!!! 持有一个Runnable对象
public void clean() {
if (remove(this)) {
try {
this.thunk.run(); //!!! 在这里调用了run方法
} catch (final Throwable var2) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (System.err != null) {
(new Error("Cleaner terminated abnormally", var2)).printStackTrace();
}
System.exit(1);
return null;
}
});
}
}
}
}
Cleaner 继承PhantomReference,PhantomReference又继承自Reference。Reference中有一个重要的类为ReferenceHandler。
这个类是个守护线程,一直伴随主线程。它就一直循环执行一个方法tryHandlePending()。
在这个tryHandlePending()中,会拿到pending对象,从而拿到对应的cleaner对象。
然后就会调用cleaner.clean()方法,其中再Deallocator的run(),然后再调用unsafe.freeMemory。
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
if (pending != null) { //当Cleaner持有的directByteBuffer对象被回收时,JVM会把该Cleaner赋值给pending
r = pending;
c = r instanceof Cleaner ? (Cleaner) r : null; //所以这里拿到pending后,也就会拿到cleaner对象
pending = r.discovered;
r.discovered = null;
} else {
if (waitForNotify) {
lock.wait();
}
// retry if waited
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
Thread.yield();
return true;
} catch (InterruptedException x) {
return true;
}
if (c != null) {
c.clean(); //!!! 在这里调用到了 cleaner.clean()方法
return true;
}
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
}
二、直接内存回收流程总结
业务代码 将 DirectByteBuffer 置为null,表示想要回收这块指向的堆外内存;
JVM垃圾回收器检测到该 DirectByteBuffer 对象不可达,将其回收,然后将它对应的虚引用对象 Cleaner 放到 Reference 的 pending 属性中;
后台守护线程 ReferenceHandler 执行 tryHandlePending() 方法。检测到 pending 属性不为空,则拿到 Cleaner 对象,然后调用 Cleaner 对象的 clean 方法;
在 Cleaner 对象的 clean() 方法中,会调用 DirectByteBuffer 的内部类 Deallocator 的 run() 方法;
在 run 方法中,会调用 unsafe.freeMemory() 方法,从而释放了堆外内存。

浙公网安备 33010602011771号