Netty自己在內存分配上支持堆內存和直接內存,咱們通常選用直接內存,這也是默認的配置。因此要理解Netty內存的釋放咱們得先看下直接內存的釋放。java
咱們先來看下直接內存是怎麼使用的jvm
ByteBuffer.allocateDirect(capacity)
申請的過程是其實就是建立一個DirectByteBuffer對象的過程,DirectByteBuffer對象只至關於一個holder,包含一個address,這個是直接內存的指針。this
public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity); } DirectByteBuffer(int cap) { // package-private // 省略中間代碼... // 建立一個cleaner,最後會調用Deallocator.run來釋放內存 cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null; }
Cleaner這個類繼承自PhantomReference,也就是所謂的虛引用,這種類型引用的特色是:spa
JVM在回前會將將要被回收的對象放在一個隊列中,因爲Cleaner繼承自PhantomReference,隊列的實現是使用cleaner的.net
private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();
這個隊列在PhantomReference的父類Reference中使用到了,Reference這個類在初始化的時候會啓動一個線程來調用cleaner.clean方法,在Reference的靜態代碼塊中啓動線程線程
// java.lang.ref.Reference static { ThreadGroup tg = Thread.currentThread().getThreadGroup(); for (ThreadGroup tgn = tg; tgn != null; tg = tgn, tgn = tg.getParent()); Thread handler = new ReferenceHandler(tg, "Reference Handler"); /* If there were a special system-only priority greater than * MAX_PRIORITY, it would be used here */ handler.setPriority(Thread.MAX_PRIORITY); handler.setDaemon(true); // 啓動ReferenceHandler線程 handler.start(); // 省略中間代碼... }
該線程的主要做用就是調用tryHandlePending指針
// java.lang.ref.Reference#tryHandlePending 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) { // 調用clean方法 c.clean(); return true; } ReferenceQueue<? super Object> q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); return true; }
System.gc不能回收堆外內存,可是會回收已經沒有使用了DirectByteBuffer對象,該對象被回收的時候會將cleaner對象放入隊列中,在Reference的線程中調用clean方法來回收堆外內存 。cleaner.run執行的是傳入參數的thunk.run方法,這裏thunk是Deallocator,因此最後執行的Deallocator.run方法netty
public void run() { if (address == 0) { // Paranoia return; } // 釋放內存 unsafe.freeMemory(address); address = 0; Bits.unreserveMemory(size, capacity); }
因此最後經過unsafe.freeMemory釋放了申請到的內存。code
總結一下,在申請內存的時候調用的是java.nio.ByteBuffer#allocateDirect
對象
會new DirectByteBuffer,經過Cleaner.create建立Cleaner,同時傳入Deallocator做爲Runnable參數,在Cleaner.clean的時候會調用該Deallocator.run來處理
Cleaner繼承自PhantomReference,包含一個ReferenceQueue,在DirectByteBuffer再也不使用的時候,該對象是處於Java堆的,除了該PhantomReference引用了DirectByteBuffer外,沒有其餘引用的時候,jvm會把cleaner對象放入ReferenceQueue隊列中。
PhantomReference繼承了Reference,Reference會啓動一個線程(java.lang.ref.Reference.ReferenceHandler#run)去調用隊列中的cleaner.clean方法。
Netty使用的直接內存的釋放方式和JDK的釋放方式略有不一樣。Netty開始釋放內存的時候是調用free方法的時候
io.netty.buffer.PoolArena#free io.netty.buffer.PoolArena.DirectArena#destroyChunk
最終釋放內存的方法有兩種
兩種不一樣的方式依賴的條件不一樣,使用場景也不一樣
要知足如下條件之一的時候使用這種方式
不能使用上面這種方式的都使用unsafe