概述
本文主要通过图,代码简要的解析DirectByteBuffer是如何回收堆外内存的。
代码解析
实际释放内存的类
释放内存的代码是:unsafe.freeMemory(address);
其中Deallocator其实是DirectByteBuffer中的内部私有类,address就是指向堆外内存的地址。
private static class Deallocator
implements Runnable
{
private static Unsafe unsafe = Unsafe.getUnsafe();
private long address;
private long size;
private int capacity;
private Deallocator(long address, long size, int capacity) {
assert (address != 0);
this.address = address;
this.size = size;
this.capacity = capacity;
}
public void run() {
if (address == 0) {
// Paranoia
return;
}
// 真正释放堆外内存的语句
unsafe.freeMemory(address);
address = 0;
Bits.unreserveMemory(size, capacity);
}
}
堆外内存地址address来自哪儿
可以看到在new直接内存类DirectByteBuffer的构造函数中,申请内存的代码,其中base就是指向堆外内存的地址
base = unsafe.allocateMemory(size);
构造函数最后,构造了Deallocator实例,传入了堆外内存地址base,即上面的address;
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
DirectByteBuffer(int cap) { // package-private
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);
long base = 0;
try {
// 申请内存的代码,base指向堆外内存
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
//把堆外内存地址传给Deallocator。并用cleaner做了封装
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
Cleaner又是什么
部分代码省略,只贴出主要代码
/** 可以看到,Cleaner继承了PhantomReference类,是个虚引用类 */
public class Cleaner extends PhantomReference<Object> {
//被GC回收的对象(构造时传入,这里指DirectByteBuffer实例),则Cleaner虚引用会入此队列
private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue();
private static Cleaner first = null;
private Cleaner next = null;
private Cleaner prev = null;
//DirectByteBuffer中trunk实际就是Deallocator实例,用于调用释放内存的方法
private final Runnable thunk;
/**
* @param var1 DirectByteBuffer
* @param var2 Deallocator实例,用于调用释放内存的方法
*/
private Cleaner(Object var1, Runnable var2) {
super(var1, dummyQueue);
this.thunk = var2;
}
public static Cleaner create(Object var0, Runnable var1) {
return var1 == null ? null : add(new Cleaner(var0, var1));
}
public void clean() {
if (remove(this)) {
try {
//Deallocator实例的释放内存
this.thunk.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;
}
});
}
}
}//end of clean()
}
如何触发内存释放
主动释放: 通过如下代码可知,通过DirectByteBuffer 的父接口 cleaner(); 可获取Cleaner 对象,然后调用
Cleaner#clean()方法即可达到释放堆外内存的效果
class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer
{
public Cleaner cleaner() { return cleaner; }
}
public interface DirectBuffer {
long address();
Object attachment();
Cleaner cleaner();
}
被动释放 通过分析java.lang.ref.Reference如下代码可知。在GC虚引用java.lang.ref.PhantomReference指向的对象时,都会把这个引用如Cleaner入到队列ReferenceQueue
这里Cleaner虚引用会入到sun.misc.Cleaner#dummyQueue队列里。在后续的ReferenceHandler中调用
Cleaner#clean();做内存释放。
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
if (pending != null) {
r = pending;
// 'instanceof' might throw OutOfMemoryError sometimes
// so do this before un-linking 'r' from the 'pending' chain...
c = r instanceof Cleaner ? (Cleaner) r : null;
// unlink 'r' from 'pending' chain
pending = r.discovered;
r.discovered = null;
} else {
// The waiting on the lock may cause an OutOfMemoryError
// because it may try to allocate exception objects.
if (waitForNotify) {
lock.wait();
}
// retry if waited
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
// Give other threads CPU time so they hopefully drop some live references
// and GC reclaims some space.
// Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
// persistently throws OOME for some time...
Thread.yield();
// retry
return true;
} catch (InterruptedException x) {
// retry
return true;
}
// Fast path for cleaners
if (c != null) {
c.clean();
return true;
}
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
}
#图
待定…
以上为个人总结笔记。如有错漏或者意见、建议,欢迎随时留言、指正。