Netty 本身在内存分配上支持堆内存和直接内存, 我们一般选用直接内存, 这也是默认的配置. 所以要理解 Netty 内存的释放我们得先看下直接内存的释放.
Java 直接内存释放
我们先来看下直接内存是怎么使用的
ByteBuffer.allocateDirect(capacity)
申请的过程是其实就是创建一个 DirectByteBuffer 对象的过程, DirectByteBuffer 对象只相当于一个 holder, 包含一个 address, 这个是直接内存的指针.
调用 native 方法申请内存
初始化 cleaner
- 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, 也就是所谓的虚引用, 这种类型引用的特点是:
使用 get 方法不能获取到对象
只要引用的对象除了 PhantomReference 之外没有其他引用了, JVM 随时可以将 PhantomReference 引用的对象回收.
JVM 在回前会将将要被回收的对象放在一个队列中, 由于 Cleaner 继承自 PhantomReference, 队列的实现是使用 cleaner 的
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 方法
- public void run() {
- if (address == 0) {
- // Paranoia
- return;
- }
- // 释放内存
- unsafe.freeMemory(address);
- address = 0;
- Bits.unreserveMemory(size, capacity);
- }
所以最后通过 unsafe.freeMemory 释放了申请到的内存.
总结一下, 在申请内存的时候调用的是
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 内存释放
Netty 使用的直接内存的释放方式和 JDK 的释放方式略有不同. Netty 开始释放内存的时候是调用 free 方法的时候
- io.netty.buffer.PoolArena#free
- io.netty.buffer.PoolArena.DirectArena#destroyChunk
最终释放内存的方法有两种
利用反射获取 unsafe, 调用 Unsafe#freeMemory
利用反射获取 DirectByteBuffer#cleaner, 通过反射调用 cleaner.clean 方法
两种不同的方式依赖的条件不同, 使用场景也不同
使用反射调用 cleaner.clean
要满足以下条件之一的时候使用这种方式
没有可使用的直接内存
不能获取 unsafe
directBuffer 没有传入 long,int 的构造方法
使用 unsafe
不能使用上面这种方式的都使用 unsafe
来源: https://www.cnblogs.com/sunshine-2015/p/9393410.html