在SunJDK中,java.nio.DirectByteBuffer
是由ByteBuffer#allocateDirect(int)
创建的,它有一个类型为sun.misc.Cleaner
的字段,Cleaner
继承了java.lang.ref.PhantomReference
(虚引用).
DirectByteBuffer
创建时cleaner
字段赋值,传入一个Runnable
类型的Deallocator
对象(用于释放内存)
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
当Cleaner
被收集并且即将移入关联的引用队列时,GC收集相关的线程通过ReferenceHandler
对Cleaner
类型的Reference
实例特殊处理:将引用实例向下转型为Cleaner
,然后调用Cleaner#clean()
,最终会回到调用DirectByteBuffer$Deallocator#run()
,进而调用Unsafe#freeMemory(long)
.
可以看到,并没有对Object#finalize()
的调用.
总之,如果释放对DirectByteBuffer
实例的引用,一般就不会耗尽内存,只要垃圾收集器有机会注意到这种情况,之后它的引用处理线程就会执行上述过程.
Cleaner对象
虚引用的子类型.对象构造时传入一个Runnable
用于在引用处理线程执行时调用.
Reference.java
注意本代码的第55行
private static class ReferenceHandler extends Thread {
private static void ensureClassInitialized(Class<?> clazz) {
try {
Class.forName(clazz.getName(), true, clazz.getClassLoader());
} catch (ClassNotFoundException e) {
throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
}
}
static {
// pre-load and initialize InterruptedException and Cleaner classes
// so that we don‘t get into trouble later in the run loop if there‘s
// memory shortage while loading/initializing them lazily.
ensureClassInitialized(InterruptedException.class);
ensureClassInitialized(Cleaner.class);
}
ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
}
public void run() {
while (true) {
tryHandlePending(true);
}
}
}
/**
* Try handle pending {@link Reference} if there is one.<p>
* Return {@code true} as a hint that there might be another
* {@link Reference} pending or {@code false} when there are no more pending
* {@link Reference}s at the moment and the program can do some other
* useful work instead of looping.
*
* @param waitForNotify if {@code true} and there was no pending
* {@link Reference}, wait until notified from VM
* or interrupted; if {@code false}, return immediately
* when there is no pending {@link Reference}.
* @return {@code true} if there was a {@link Reference} pending and it
* was processed, or we waited for notification and either got it
* or thread was interrupted before being notified;
* {@code false} otherwise.
*/
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;
}
原文:https://www.cnblogs.com/onion94/p/Java-DirectByteBuffer-How-Release.html