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