b2科目四模拟试题多少题驾考考爆了怎么补救
b2科目四模拟试题多少题 驾考考爆了怎么补救

Netty堆外内存泄漏问题排查思路提供参考知识点解析

电脑杂谈  发布时间:2021-02-22 09:06:21  来源:网络整理

java内存泄漏检测工具

上一篇文章介绍了Netty内存模型的原理。由于Netty使用不当,将导致堆外内存泄漏。 Internet上关于此的信息相对较少,因此我写这篇文章专门介绍Netty堆外内存的故障排除。知识点,诊断工具和故障排除想法可提供参考

现象

堆外内存泄漏的主要现象是该进程占用大量内存(您可以在Linux下使用top命令查看),而Java堆内存占用不高(使用jmap命令查看),并且Netty以外的堆外存储器的常用用法,还有基于java.nio,JNI调用等相关接口的堆外存储器应用程序。以下着重于Netty堆外存储器泄漏的故障排除

堆外内存发行版1的基础实现java.nio堆外内存发行版

Netty堆外内存是基于本机java.nio的DirectByteBuffer对象实现的,因此有必要首先了解其释放原理

java.nio提供的

DirectByteBuffer提供sun.misc.Cleaner类的clean()方法。进行系统调用以释放堆外内存,并且在两种情况下会触发clean()方法

ByteBuffer buf = ByteBuffer.allocateDirect(1);
((DirectBuffer) byteBuffer).cleaner().clean();

Cleaner类继承了java.lang.ref.Reference,GC线程将设置Reference的内部变量(待处理变量是链接列表的头节点,发现的变量是下一个链接列表节点),可以回收利用且无法访问的参考对象被组织在一个链表中

引用的内部守护线程从链接列表的开头消耗数据。如果消耗的Reference对象的类型也为Cleaner,则线程将调用clean()方法(Reference#tryHandlePending())

2 Netty noCleaner策略

在引入noCleaner策略之前,您需要了解带有Cleaner对象的DirectByteBuffer在初始化过程中的作用:

仅使用DirectByteBuffer(int cap)构造方法初始化Cleaner对象。在该方法中,检查当前内存是否超出了允许的最大堆外内存(可通过-XX:MaxDirectMemorySize配置)

如果超过,它将首先尝试将无法访问的Reference对象添加到Reference列表中,并且依赖于Reference的内部守护程序线程将触发与可回收的DirectByteBuffer关联的Cleaner的run()方法。

如果内存仍然不足,请执行System.gc()来触发完整的gc,以回收堆内存中的DirectByteBuffer对象,以触发堆外内存恢复,如果仍然超出限制,则抛出java.lang。 OutOfMemoryError(代码位于java.nio.Bits#reserveMemory()方法中)

Netty在4. 1中引入了noCleaner策略:创建一个没有Cleaner的DirectByteBuffer对象。这样做的好处是,绕过Directer的Cleaner执行构造方法以及Cleaner的clean()方法中的一些额外开销。当堆外内存不足时,将不会触发System.gc()来提高性能

hasCleaner的DirectByteBuffer和noCleaner的DirectByteBuffer之间的主要区别如下:

注意:Unsafe是位于sun.misc包中的类,它可以提供本地方法,例如内存操作,对象操作,线程调度等。这些方法在提高Java操作效率和提高Java效率方面起着非常重要的作用。增强Java语言基础资源的操作能力。效果很好,但是错误使用Unsafe类会增加程序错误的可能性,并且该程序不再“安全”,因此政府不建议使用,并且可能在以后的jdk版本中删除

Netty启动时,需要判断当前环境和环境配置参数是否允许使用noCleaner策略(特定逻辑位于PlatformDependent的静态代码块中)。例如,在Android下运行时,没有Unsafe类并且不允许noCleaner策略。如果不允许,请使用hasCleaner策略

注意:您可以调用PlatformDependent.useDirectBufferNoCleaner()方法来检查当前Netty程序是否使用noCleaner策略

阅读此内容后,一些读者可能会问Netty是否基于hasCleaner策略通过GC触发Cleaner.clean()并自动回收堆外内存,是否有可能忽略ByteBuf.release()的调用方法?内存会泄漏吗?

当然不是。一方面,原因是自动触发不是实时的:GC线程需要回收ByteBuffer对象才能触发。如果ByteBuffer对象在输入旧数据后变得可回收,则需要等待直到旧GC的发送频率将触发

另一方面,Netty需要基于ByteBuf.release()方法执行其他操作,例如将池化的内存释放回内存池,否则该对象将始终被标记为已被内存池使用

ByteBuf.release()触发机制

在行业中存在一个误解,即由Netty框架分配的ByteBuf将被自动释放,而业务则不需要被释放;业务创建的ByteBuf需要自行释放,而Netty框架不会释放它。

内存泄漏检测工具java_java 内存泄漏_java内存泄漏检测工具

这种误解是有原因的。在某些情况下,Netty框架将调用ByteBuf.release()方法:

1入站邮件处理

在处理入站消息时,Netty将创建一个ByteBuf来读取通道上的消息,并触发对管道上ChannelHandler的调用以对其进行处理。由使用ByteBuf的应用程序定义的ChannelHandler需要负责release()

public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ByteBuf buf = (ByteBuf) msg;
    try {
        ...
    } finally {
        buf.release();
    }
}

如果当前ChannelHandler未处理ByteBuf,它将被传递到管道中的下一个处理程序:

public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ByteBuf buf = (ByteBuf) msg;
    ...
    ctx.fireChannelRead(buf);
}

很常见,我们将通过继承ChannelInboundHandlerAdapter定义用于入站消息处理的处理程序。在这种情况下,如果所有程序的处理程序都未调用release()方法,则入站消息Netty最终不会释放(),这将导致内存泄漏;

当在管道处理程序处理中引发异常时,Netty框架最终将捕获该异常并执行ByteBuf.release();

完整过程位于AbstractNioByteChannel.NioByteUnsafe#read()中,其关键片段提取如下:

try {
	do {
		byteBuf = allocHandle.allocate(allocator);
		allocHandle.lastBytesRead(doReadBytes(byteBuf));
		// 入站消息已读完
		if (allocHandle.lastBytesRead() <= 0) {
            // ...
			break;
		}
        // 触发pipline上handler进行处理
		pipeline.fireChannelRead(byteBuf);
		byteBuf = null;
	} while (allocHandle.continueReading());
	// ...
} catch (Throwable t) {
    // 异常处理中包括调用 byteBuf.release()
	handleReadException(pipeline, byteBuf, t, close, allocHandle);
} 

但是,它也通常用于通过继承SimpleChannelInboundHandler来定义入站消息处理,这将确保最终释放消息:

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
	boolean release = true;
	try {
	    // 该消息由当前handler处理
		if (acceptInboundMessage(msg)) {
			I imsg = (I) msg;
			channelRead0(ctx, imsg);
		} else {
		    // 不由当前handler处理,传递给pipeline上下一个handler
			release = false;
			ctx.fireChannelRead(msg);
		}
	} finally {
	    // 触发release
		if (autoRelease && release) {
			ReferenceCountUtil.release(msg);
		}
	}
}

2出站邮件处理

与Netty框架自动创建的入站消息不同,出站消息通常由应用程序创建,然后调用基于通道的write()方法或writeAndFlush()方法。这些方法在内部负责调用传入的byteBuf release()方法

注意:netty- 4. 0. 0. CR2之前的版本中的write()方法存在问题,并且不会调用ByteBuf.release()

3 release()注意事项

还有一个常见的误解,即只要调用ByteBuf的release()方法或ReferenceCountUtil.release()方法,就可以保证对象的内存被释放,但是不会被释放。

由于Netty的ByteBuf引用计数管理ByteBuf对象的生命周期,因此在调用release()方法时,ByteBuf继承了ReferenceCounted接口并提供了外部keep()和release()方法来增加或减少引用计数值。 ,内部计数值减小为0以触发内存回收操作

派生的意思是派生的。在ByteBuf.duplicate(),ByteBuf.slice()和ByteBuf.order(ByteOrder)方法中,将创建派生的ByteBuf。创建的ByteBuf与原始ByteBuf共享参考计数。最初的ByteBuf的release()方法调用也会导致这些对象的内存被回收

相反,由ByteBuf.copy()和ByteBuf.readBytes(int)方法创建的对象不是派生的ByteBuf。这些对象和原始ByteBuf不共享引用计数,并且原始ByteBuf release()方法调用不会导致这些对象被回收

堆外内存大小控制参数

用于配置堆外内存大小的参数为-XX:MaxDirectMemorySize和ty.maxDirectMemory。这两个参数有什么区别?

注意:-XX:MaxDirectMemorySize不能限制Netty中noCleaner策略的DirectByteBuffer堆外内存的大小

堆外内存监视

如何使用堆外内存?

内存泄漏检测工具java_java 内存泄漏_java内存泄漏检测工具

1个代码工具

注意:MXBean是Java提供的用于监视统计信息的一系列特殊bean,通过不同类型的MXBean,您可以获得诸如JVM进程的内存,线程和类加载信息之类的监视指标

List bufferPoolMXBeans = ManagementFactoryHelper.getBufferPoolMXBeans();
BufferPoolMXBean directBufferMXBean = bufferPoolMXBeans.get(0);
// hasCleaner的DirectBuffer的数量
long count = directBufferMXBean.getCount();
// hasCleaner的DirectBuffer的堆外内存占用大小,单位字节
long memoryUsed = directBufferMXBean.getMemoryUsed();

注意:MappedByteBuffer:这是通过基于FileChannelImpl.map的mmap内存映射(零复制的实现)获得的另一个堆外内存ByteBuffer,可以通过ManagementFactoryHelper.getBufferPoolMXBeans()。get(1)获取堆外内存的监视指标

2 Netty带有内存泄漏检测工具

Netty还附带了一个内存泄漏检测工具,该工具可用于检测GC已回收ByteBuf对象,但尚未释放由ByteBuf管理的内存,但不适用于这种情况。 GC尚未回收ByteBuf对象的位置。内存泄漏,例如任务队列积压

为了方便用户发现内存泄漏,Netty提供了4种检测级别:

使用方法是在命令行上设置参数:

-Dio.netty.leakDetectionLevel=[检测级别]

示例程序如下,将检测级别设置为偏执:

// -Dio.netty.leakDetectionLevel=paranoid
public static void main(String[] args) {
	for (int i = 0; i < 500000; ++i) {
		ByteBuf byteBuf = UnpooledByteBufAllocator.DEFAULT.buffer(1024);
		byteBuf = null;	
	}
	System.gc();
}

您可以看到控制台输出泄漏报告:

十二月 27, 2019 8:37:04 上午 io.netty.util.ResourceLeakDetector reportTracedLeak
严重: LEAK: ByteBuf.release() was not called before it's garbage-collected. See https://netty.io/wiki/reference-counted-objects.html for more information.
Recent access records: 
Created at:
	io.netty.buffer.UnpooledByteBufAllocator.newDirectBuffer(UnpooledByteBufAllocator.java:96)
	io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:187)
	io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:178)
	io.netty.buffer.AbstractByteBufAllocator.buffer(AbstractByteBufAllocator.java:115)
	org.caison.netty.demo.memory.BufferLeaksDemo.main(BufferLeaksDemo.java:15)

内存泄漏的原理是使用弱引用。弱引用(WeakReference)在创建时需要指定一个引用队列(refQueue)。通过使用弱引用包装ByteBuf对象(代码条目位于AbstractByteBufAllocator#toLeakAwareBuffer()方法中)

发生GC时,如果GC线程检测到ByteBuf对象仅与弱引用对象相关联,则它将WeakReference添加到refQueue;

正常释放ByteBuf内存时,将调用WeakReference的clear()方法以删除对ByteBuf的引用,并且后续的GC线程不会将WeakReference添加到refQueue;

Netty每次根据采样率创建一个ByteBuf时,都会在采样达到时轮询refQueue中的WeakReference对象。与轮询返回的非空WeakReference关联的ByteBuf是泄漏的堆外内存(代码入口在ResourceLeakDetector#track()方法中)

3种图形工具

在获取堆外内存的代码的基础上,通过自定义访问某些监视工具来定期检测和获取并绘制图形,例如较流行的Prometheus或Zabbix

您还可以通过jdk附带的Visualvm来获取它,您需要安装缓冲池插件。基本原理是访问MXBean中的监视指示器,并且只能使用hasCleaner的DirectByteBuffer

java内存泄漏检测工具

此外,对于JNI调用生成的堆外内存分配,您可以使用google-perftools进行监视

堆外内存泄漏诊断

堆外内存泄漏有许多特定原因。首先介绍对任务队列累积的监视,然后介绍一般的堆外内存泄漏诊断思路

1个任务队列累积

java内存泄漏检测工具_内存泄漏检测工具java_java 内存泄漏

此处的任务队列是值NioEventLoop中的Queue taskQueue。提交到任务队列的方案是:

ctx.channel().eventLoop().execute(runnable);

channel.write(...)
channel.writeAndFlush(...)

ctx.channel().eventLoop().schedule(runnable, 60, TimeUnit.SECONDS);

当队列中的任务太多时,消息将无法写入通道然后再释放,这将导致内存泄漏

诊断思想是监视任务队列中的任务数量,ByteBuf的待办事项大小以及任务信息。具体的监控程序如下(代码地址):

public void channelActive(ChannelHandlerContext ctx) throws NoSuchFieldException, IllegalAccessException {
	monitorPendingTaskCount(ctx);
	monitorQueueFirstTask(ctx);
	monitorOutboundBufSize(ctx);
}
/** 监控任务队列堆积任务数,任务队列中的任务包括io读写任务,业务程序提交任务 */
public void monitorPendingTaskCount(ChannelHandlerContext ctx) {
	int totalPendingSize = 0;
	for (EventExecutor eventExecutor : ctx.executor().parent()) {
		SingleThreadEventExecutor executor = (SingleThreadEventExecutor) eventExecutor;
		// 注意,Netty4.1.29以下版本本pendingTasks()方法存在bug,导致线程阻塞问题
		// 参考 https://github.com/netty/netty/issues/8196
		totalPendingSize += executor.pendingTasks();
	}
	System.out.println("任务队列中总任务数 = " + totalPendingSize);
}
/** 监控各个堆积的任务队列中第一个任务的类信息 */
public void monitorQueueFirstTask(ChannelHandlerContext ctx) throws NoSuchFieldException, IllegalAccessException {
	Field singleThreadField = SingleThreadEventExecutor.class.getDeclaredField("taskQueue");
	singleThreadField.setAccessible(true);
	for (EventExecutor eventExecutor : ctx.executor().parent()) {
		SingleThreadEventExecutor executor = (SingleThreadEventExecutor) eventExecutor;
		Runnable task = ((Queue) singleThreadField.get(executor)).peek();
		if (null != task) {
			System.out.println("任务队列中第一个任务信息:" + task.getClass().getName());
		}
	}
}
/** 监控出站消息的队列积压的byteBuf大小 */
public void monitorOutboundBufSize(ChannelHandlerContext ctx) {
	long outBoundBufSize = ((NioSocketChannel) ctx.channel()).unsafe().outboundBuffer().totalPendingWriteBytes();
	System.out.println("出站消息队列中积压的buf大小" + outBoundBufSize);
}

在基于Netty的实际业务开发中,如何处理耗时的业务逻辑代码?

让我们先谈谈结论。建议自定义一组新的业务线程池,然后将耗时的业务提交给业务线程池

Netty的工作线程(NioEventLoop)除了将连接数据读取处理为NIO线程外,还在管道上执行channelHandler逻辑,并使用在taskQueue中提交的任务,包括通道写操作。

如果将耗时的任务提交给taskQueue,它也会影响NIO线程的处理以及taskQueue中的任务。因此,建议将处理隔离在单独的业务线程池中

2个一般诊断思路

Netty发生堆外内存泄漏的原因很多,例如缺少调用release()的代码;通过retain()增加ByteBuf的参考计数值,但是在调用release()时不会清除参考计数值;由于Exception导致release()失败; ByteBuf引用的对象预先是GC,并且相关的堆外内存无法回收,等等,因此无法在此处列出,因此请尝试提供一组常规诊断思路以供参考

首先,需要重现该问题。为了不影响服务的运行,请尝试在测试环境或本地环境中进行模拟。但是,这些环境通常不具有与相同的并发量,可以通过压力测试工具来模拟请求。

对于某些无法模拟的场景,可以通过Linux流量复制工具将实际的流量复制到测试环境,而不会影响业务。类似的工具包括Gor,tcpreplay,tcpcopy等。

再现后,下一步是查找问题,首先尝试直接通过上面介绍的监视方法和日志信息来查找问题;

如果找不到它,则需要找到堆外内存泄漏的触发条件,但有时应用程序相对较大,并且提供了许多通往外部世界的流量入口,无法检查一一。

在非环境中,您可以注释掉流量条目,每次注释掉一半,然后运行以检查问题是否仍然存在,如果存在,请继续注释掉剩余的一半,直到经过几次尝试,该策略可以快速找到问题的触发条件

找到触发条件后,请在程序中检查触发条件的处理逻辑。如果处理过程非常复杂且无法直接看到,则可以继续注释掉部分代码并检查二分法,直到特定的问题代码块为止

整个想法的核心在于问题的重现,监视和消除方法。它也可以用于解决其他问题,例如堆内存泄漏,CPU 100%,服务进程挂起等。

摘要

整篇文章着重介绍知识点和理论,但缺乏实际联系。以下是一些高质量的博客文章:

“ Netty堆外内存泄漏调查盛宴” Flash显示了如何调试堆外内存泄漏。

《 Netty防止内存泄漏的措施》,《 Netty权威指南》,华为李林峰内存泄漏知识共享

美团技术团队季兵的案例“神秘跟踪:春季启动内存泄漏调查”

“ Netty简介和实战:模仿微信即时通讯系统”,Flash Nuggets手册(收费),个人将学习此专栏以开始使用Netty

java内存泄漏检测工具


本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/shoujiruanjian/article-359694-1.html

    相关阅读
      发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

      热点图片
      拼命载入中...