原文:http://lovestblog.cn/blog/2015/05/12/direct-buffer/
这里只截取自己要的
广义的堆外内存:新生代、老生代、持久代之外的内存,包括jvm本身在运行过程中分配的内存,jni里分配的内存,以及下面提到的DirectByteBuffer分配的内存等等
狭义的堆外内存:主要指java.nio.DirectByteBuffer在创建时分配的内存,本文也指的是狭义的堆外内存
System.gc
堆外内存不会对gc造成什么影响(System.gc除外),堆外内存的回收其实依赖于我们的gc机制。首先在java层面和这块堆外内存关联的只有与之关联的DirectByteBuffer对象,它记录了这块内存的基地址以及大小,gc能通过操作DirectByteBuffer对象来间接操作对应的堆外内存。
DirectByteBuffer对象在创建的时候关联了一个PhantomReference,PhantomReference是用于跟踪对象何时被回收的,它不能影响gc决策,但是gc过程中如果返现某个对象除了PhantomReference外没有其他地方引用,就会将这个引用java.lang.ref.Reference.pending队列里,在gc完毕时通知ReferenceHandler这个守护线程去执行一些后置处理,而DirectByteBuffer关联的PhantomReference是PhantomReference的一个子类,在最终处理里会通过Unsafe的free接口来释放DirectByteBuffer对应的对外内存块
JDK里ReferenceHandler实现:
private static class ReferenceHandler extends Thread { ReferenceHandler(ThreadGroup g, String name) { super(g, name); } public void run() { for (;;) { Reference r; synchronized (lock) { if (pending != null) { r = pending; Reference rn = r.next; pending = (rn == r) ? null : rn; r.next = r; } else { try { lock.wait(); } catch (InterruptedException x) { } continue; } } // Fast path for cleaners if (r instanceof Cleaner) { ((Cleaner)r).clean(); continue; } ReferenceQueue q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); } } }
如果pending为空的时候,会通过lock.wait()一直等在那里,唤醒动作是在jvm里做的,在gc完成后会调用如下方法VM_GC_Operation::doit_epilogue(),在方法末尾会调用lock的notify操作,至于pending队列是什么时候讲引用放进去,其实是在gc的引用处理逻辑中放进去的
void VM_GC_Operation::doit_epilogue() { assert(Thread::current()->is_Java_thread(), "just checking"); // Release the Heap_lock first. SharedHeap* sh = SharedHeap::heap(); if (sh != NULL) sh->_thread_holds_heap_lock_for_gc = false; Heap_lock->unlock(); release_and_notify_pending_list_lock(); } void VM_GC_Operation::release_and_notify_pending_list_lock() { instanceRefKlass::release_and_notify_pending_list_lock(&_pending_list_basic_lock); }
为什么要使用堆外内存以及为什么不能大面积使用堆外内存看原文