Netty 内存回收之 noCleaner 策略
前言
对于堆外内存,使用 System.gc() 是不靠谱的,依赖老年代 FGC 也是不靠谱的,而且大部分调优指南都设置了 -DisableExplicitGC 禁用 System.gc()。所以主动回收比较靠谱, JDK 在 DirectByteBuffer 中提供了 Cleaner 用来主动释放内存。同时还有 Unsafe 的 freeMemory 方法也可以。 下面看看他是怎么做的。这里以非池化创建直接内存为例。
UnpooledByteBufAllocator newDirectBuffer 方法
代码如下:
protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
final ByteBuf buf;
if (PlatformDependent.hasUnsafe()) {
buf = noCleaner ? new InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf(this, initialCapacity, maxCapacity) :
new InstrumentedUnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);
} else {
buf = new InstrumentedUnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
}
return disableLeakDetector ? buf : toLeakAwareBuffer(buf);
}
关键点在于 noCleaner 的结果。影响其结果的代码如下:
// 构造方法,
public UnpooledByteBufAllocator(boolean preferDirect, boolean disableLeakDetector, boolean tryNoCleaner) {
super(preferDirect);
this.disableLeakDetector = disableLeakDetector;
noCleaner = tryNoCleaner && PlatformDependent.hasUnsafe()
&& PlatformDependent.hasDirectBufferNoCleanerConstructor();
}
// tryNoCleaner 结果来自 PlatformDependent.useDirectBufferNoCleaner()
public UnpooledByteBufAllocator(boolean preferDirect, boolean disableLeakDetector) {
this(preferDirect, disableLeakDetector, PlatformDependent.useDirectBufferNoCleaner());
}
// 判断是否含有 DirectByteBuffer 构造器,有则 true
public static boolean useDirectBufferNoCleaner() {
return USE_DIRECT_BUFFER_NO_CLEANER;
}
// 根据是否含有 DirectByteBuffer 的构造器判断,如果没有,USE_DIRECT_BUFFER_NO_CLEANER=false
if (maxDirectMemory == 0 || !hasUnsafe() || !PlatformDependent0.hasDirectBufferNoCleanerConstructor()) {
USE_DIRECT_BUFFER_NO_CLEANER = false;
DIRECT_MEMORY_COUNTER = null;
} else {
USE_DIRECT_BUFFER_NO_CLEANER = true;
if (maxDirectMemory < 0) {
maxDirectMemory = maxDirectMemory0();
if (maxDirectMemory <= 0) {
DIRECT_MEMORY_COUNTER = null;
} else {
DIRECT_MEMORY_COUNTER = new AtomicLong();
}
} else {
DIRECT_MEMORY_COUNTER = new AtomicLong();
}
}
// 获取 DirectByteBuffer 的构造器
final ByteBuffer direct;
Constructor<?> directBufferConstructor;
long address = -1;
try {
final Object maybeDirectBufferConstructor =
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
final Constructor<?> constructor =
direct.getClass().getDeclaredConstructor(long.class, int.class);
Throwable cause = ReflectionUtil.trySetAccessible(constructor, true);
if (cause != null) {
return cause;
}
return constructor;
} catch (NoSuchMethodException e) {
return e;
} catch (SecurityException e) {
return e;
}
}
});
((Constructor<?>) maybeDirectBufferConstructor).newInstance(address, 1);
directBufferConstructor = (Constructor<?>) maybeDirectBufferConstructor;
} finally {
if (address != -1) {
UNSAFE.freeMemory(address);
}
}
DIRECT_BUFFER_CONSTRUCTOR = directBufferConstructor;
noCleaner 为 true:创建 InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf 对象。简称 noCleaner;
noCleaner 为false:创建 InstrumentedUnpooledUnsafeDirectByteBuf 对象。简称 hasCleaner;
两者构造器方式不同:
noCleaner 反射调用 private DirectByteBuffer(long addr, int cap)
hasCleaner new 操作调用 DirectByteBuffer(int cap)
两个释放内存方式不同:
noCleaner 使用 unSafe.freeMemory(address);
hasCleaner 使用 DirectByteBuffer 的 Cleaner 的 clean 方法。
hasCleaner 的 clean 方法有 2 种策略:
1.Java9 使用 Unsafe 的 invokeCleaner 方法。调用了 ByteBuffer 的 Cleaner 的 clean 方法。
2. Java6 --- Java9 使用 DirectByteBuffer 的 属性 Cleaner 的 clean 方法。
clean 方法原理:
这个 clean 方法内部调用了一个名为 thunk 的 Deallocator 线程的 run 方法。该线程对象在创建 DirectByteBuffer 的时候同时创建。该线程的 run 方法内部会调用 unsafe 的 freeMemory 方法,同时还会调用 Bits.unreserveMemory 方法,该方法会相应的减小已经使用的内存大小数字(因为,每次申请直接内存都需要 Bits 判断是否足够,如果 FGC 后还不够,OOM,所以,这里的做法还是挺重要的)
注意:这个 Cleaner 是个虚引用,DirectByteBuffer 创建他的时候,会将自己放入虚引用的构造函数中,如果这个 DirectByteBuffer 被回收了(无人再引用这个 Cleaner),那么 GC 将会把这个 Cleaner 赋值给 Reference 的 pending 变量中,专门有一条 ReferenceHandler 的线程会死循环执行 Reference 的 tryHandlePending 方法,这个方法会调用 pending 的 clean 方法,完成内存回收操作。
这是 cleaner 对象的构造时机:
DirectByteBuffer(int cap) { // package-private
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);
long base = 0;
try {
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
// 这里构造 cleaner
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
该构造只有一个 int 参数
所以,你知道了吧,noCleaner 的构造方法是不能调用 cleaner 的 clean 方法的。只能使用 unSafe 的 freeMemory 方法。而这就是 Netty 默认的做法。
同时,noCleaner 的构造方法也没有向 Bits 申请内存的内容,在申请内存的时候,性能会比 hasCleaner 要好一点。关于 Bits 的设计,我觉得不够优雅。当内存不够了,就 System.gc(),却只休眠 100 毫秒。根本不够回收到堆外内存。
实际上,Cleaner 的作用除了更新一下 Bits 的一些属性,方便下次申请内存之外,别无作用。
我猜想 Netty 使用 noCleaner 是性能优化的考虑吧。为了防止用户忘记使用 ReferenceCountUtil.release(), 导致内存泄漏,Netty 还使用了虚引用跟踪每一个 ByteBuf,基本上避免了内存泄漏的发生。
综上所述:noCleaner 无论是在申请内存还是释放内存都比使用 hasCleaner 性能好要好一点。
Netty 内存回收之 noCleaner 策略的更多相关文章
- netty内存数据缓冲区使用策略
主要是通过AbstractByteBufAllocator类实现的ByteBuffer的申请. 代码如下: @Override public ByteBuf ioBuffer(int initialC ...
- Redis内存回收策略
如果使用Redis的时候,不合理使用内存,把什么东西都放在内存里面,又不设置过期时间,就会导致内存的堆积越来越大.根据28法则,除了20%的热点数据之外,剩余的80%的非热点或不怎么重要的数据都在占用 ...
- Redis的内存回收策略和内存上限(阿里)
还有一篇文章 讲解guava如何删除过期数据的,与redis不同,guava没有维护线程删除过期key,只是在设置 key 或者 读取key的时候,顺带删除参考:GuavaCache简介(一)是轻量级 ...
- JAVA虚拟机:内存回收策略及算法
java虚拟机中的程序计数器区.虚拟机栈区.本地方法栈区3个区域是随着线程的创建而创建,随着线程的结束而结束时,内存自然得到回收,所以这三个区域不需要过多考虑内存的回收问题. java虚拟机中的方法区 ...
- Redis的内存回收原理,及内存过期淘汰策略详解
Redis 内存回收机制Redis 的内存回收主要围绕以下两个方面: 1.Redis 过期策略:删除过期时间的 key 值 2.Redis 淘汰策略:内存使用到达 maxmemory 上限时触发内存淘 ...
- Netty内存池ByteBuf 内存回收
内存池ByteBuf 内存回收: 在前面的章节中我们有提到, 堆外内存是不受JVM 垃圾回收机制控制的, 所以我们分配一块堆外内存进行ByteBuf 操作时, 使用完毕要对对象进行回收, 本节就以Po ...
- Android内存回收机制
退出但不关闭: 这是Android对于Linux的优化.当 Android 应用程序退出时,并不清理其所占用的内存,Linux 内核进程也相应的继续存在,所谓“退出但不关闭”.从而使得用户调用程序时能 ...
- Java内存回收机制
在Java中,它的内存管理包括两方面:内存分配(创建Java对象的时候)和内存回收,这两方面工作都是由JVM自动完成的,降低了Java程序员的学习难度,避免了像C/C++直接操作内存的危险.但是,也正 ...
- linux内存回收 内核参数
ss -atu| awk '/^tcp/{++S[$2]} END {for(a in S) print a,S[a]}' ps up pid (RSS:实际内存大小,长驻内存) ps o pid,c ...
随机推荐
- 木马入侵查杀 linux
目 录: 一.问题现象: 二.问题排查: 1.netstat 排查: 2.top查看: 3.lsof -c 命令排查: 4.确定中木马了. 三.木马查杀: 木马1,清除: 木马2,清除: 四.后续处 ...
- 关于getProperties的一点记录
写了一很简单的获取配置文件的代码,结果怎么都在报空指针,经过上网查,直到要这样写才不会报: InputStream is = getClass().getClassLoader().getResour ...
- [WC2005]双面棋盘(线段树+并查集)
线段树+并查集维护连通性. 好像 \(700ms\) 的时限把我的常数超级大的做法卡掉了, 必须要开 \(O_2\) 才行. 对于线段树的每一个结点都开左边的并查集,右边的并查集,然后合并. \(Co ...
- " XSS易容术---bypass之编码混淆篇+辅助脚本编写"
一.前言本文原创作者:vk,本文属i春秋原创奖励计划,未经许可禁止转载!很多人对于XSS的了解不深.一提起来就是:“哦,弹窗的”.”哦,偷cookie的.”骚年,你根本不知道什么是力量.虽然我也不知道 ...
- Linux学习笔记-基本操作3
1. vim编辑器的使用2. gcc编译器3. 静态库的制作 -- lib4. 动态库的制作 -- dll vi -- vimvim是从vi发展过来的一款文本编辑器vi a.txt前提: 安装了 ...
- [JavaScript] Nginx实现跨域设置
假如跨域请求的接口为:http://xxx.cn/was5/web/search Nginx配置: 在conf/nginx.conf文件中 location / { root html; index ...
- Vue 项目配置
配置Vue的app项目首先需要配置本地环境. 1.下载node.js并且安装.(根据自己电脑参数进行选择) 打开cmd,检查是否安装成功. 分别输入: node -v npm -v 结果如图正确显示出 ...
- python线程死锁与递归锁
死锁现象 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去. 此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待 ...
- Vim实用技巧系列 - 利用百度云和git实现vim配置多机共享
Vim是一个强大的文本编辑器.良好的配置更能便利对Vim的使用.有时候,我们会在几台不同的电脑上使用Vim. 例如,我们可能在自己的电脑和公司的电脑上都安装了Vim. 有时候,我们需要实现,如果我们配 ...
- 3DMax——基础
1.首次打开3DMAX设置单位: 自定义→单位设置→①系统单位设置→1单位=1.0毫米:②公制→毫米 注:室内单位为毫米,室外单位为米 2.从CAD导出可以导入到3DMAX的文件: 选中要导出的部分→ ...