分享web开发知识

注册/登录|最近发布|今日推荐

主页 IT知识网页技术软件开发前端开发代码编程运营维护技术分享教程案例
当前位置:首页 > 软件开发

Netty源码—七、内存释放

发布时间:2023-09-06 02:07责任编辑:赖小花关键词:暂无标签

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.Referencestatic { ???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#tryHandlePendingstatic 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#freeio.netty.buffer.PoolArena.DirectArena#destroyChunk

最终释放内存的方法有两种

  1. 利用反射获取unsafe,调用Unsafe#freeMemory
  2. 利用反射获取DirectByteBuffer#cleaner,通过反射调用cleaner.clean方法

两种不同的方式依赖的条件不同,使用场景也不同

使用反射调用cleaner.clean

要满足以下条件之一的时候使用这种方式

  1. 没有可使用的直接内存
  2. 不能获取unsafe
  3. directBuffer没有传入long、int的构造方法

使用unsafe

不能使用上面这种方式的都使用unsafe

Netty源码—七、内存释放

原文地址:https://www.cnblogs.com/sunshine-2015/p/9393410.html

知识推荐

我的编程学习网——分享web前端后端开发技术知识。 垃圾信息处理邮箱 tousu563@163.com 网站地图
icp备案号 闽ICP备2023006418号-8 不良信息举报平台 互联网安全管理备案 Copyright 2023 www.wodecom.cn All Rights Reserved