记录一次OOM排查经历
我是用了netty搭建了一个UDP接收日志,堆启动配置 Xmx256 Xms256 ,项目刚启动的时候,系统进程占用内存很正常,在250M左右。
长时间运行之后发现,进程占用内存不断增长,远远超过了我设置的堆内存大小,查看幸存者,伊甸园,老年代,gc都很正常,堆使用数据一切正常,甚至我怀疑元空间占用内存大,查询之后发现,元空间也只用很小,而且自从程序启动开始,浮动很小。为此,我又把JVM相关知识点又拿出来翻了一遍
那么多出来的内存使用是从哪里来的?
后来通过查询相关资料才发现,Java进程内存分为堆内存,堆外内存,堆外内存是不受JVM的GC管理的。
堆外内存又是哪里使用到的?
nio框架会使用到
难道netty没有自己的一套GC机制?
有的,但是netty的GC,只负责释放自己产生的内存,如果是使用过程中,自己创建的,是不在netty GC的范围内的。好,那么现在稳定定位到了,开始修改代码和程序启动参数。
java -jar -Xms256M -Xmx256M -XX:MaxDirectMemorySize=128M -Dspring.profiles.active=prod log-server.jar
-XX:MaxDirectMemorySize=128M 设置堆外内存为128M,来控制进程内存使用,并且在代码中手动 copy 出来的 ByteBuf 进行 clear (PS:后来发现这个操作不起效果,是我对于该方法的理解有误)
@Component
public class UDPInboundHandler extends SimpleChannelInboundHandler<DatagramPacket> { private Logger logger = LoggerFactory.getLogger(UDPInboundHandler.class); @Autowired
LogService logService; @Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) {
String remoteAddr = packet.sender().getAddress().getHostAddress();
ByteBuf buf = packet.copy().content();
logService.process(buf, remoteAddr);
buf.clear();
}
}
这样运行一点时间后,嗯,内存增长速度慢下来不少,原本从两百兆涨到五百兆,只需要半天时间,现在,一天观察下来,才增长到四百多兆,但是,256+128=384M,也超过了我设置的堆内存+堆外内存的总和,而且代码开始报错了如下:
io.netty.util.internal.OutOfDirectMemoryError: failed to allocate 16777216 byte(s) of direct memory (used: 134217728, max: 134217728)
134217728 byte(s) = 128M 也就是说我的clear的操作并没有效果,堆外内存已经全部用光。OOM的报错已经刷屏,但是,在众多的异常日志中发现了这条日志
2019-09-25 18:20:00.551 {nioEventLoopGroup-2-1} ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information.
Recent access records:
Created at:
io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:331)
io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:185)
io.netty.buffer.UnsafeByteBufUtil.copy(UnsafeByteBufUtil.java:436)
io.netty.buffer.PooledUnsafeDirectByteBuf.copy(PooledUnsafeDirectByteBuf.java:309)
io.netty.buffer.AbstractByteBuf.copy(AbstractByteBuf.java:1190)
io.netty.buffer.WrappedByteBuf.copy(WrappedByteBuf.java:874)
io.netty.channel.socket.DatagramPacket.copy(DatagramPacket.java:47)
com.tutorgroup.base.logserver.server.UDPInboundHandler.channelRead0(UDPInboundHandler.java:24)
com.tutorgroup.base.logserver.server.UDPInboundHandler.channelRead0(UDPInboundHandler.java:13)
io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:105)
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434)
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965)
io.netty.channel.nio.AbstractNioMessageChannel$NioMessageUnsafe.read(AbstractNioMessageChannel.java:93)
io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:644)
io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:579)
io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:496)
io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:458)
io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
java.lang.Thread.run(Thread.java:748)
ByteBuf 没有调用 release 方法,由于我的代码量比较小,项目中只有一处是用到了 ByteBuf ,所以我很快定位到了问题代码,但是如果项目很大,不知道是哪段代码导致的问题,怎么排查呢?查询相关资料后,我们再修改一下启动参数
java -jar -Xms256M -Xmx256M -XX:MaxDirectMemorySize=2M -Dio.netty.leakDetection.level=advanced -Dio.netty.leakDetection.maxRecords=10 -Dspring.profiles.active=prod log-server.jar
果不其然,代码又报错了,这次报错的信息很详细,已经定位到是哪个ByteBuf 变量了
2019-09-27 10:54:24.442 {nioEventLoopGroup-2-1} ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information.
Recent access records:
#1:
io.netty.buffer.AdvancedLeakAwareByteBuf.readBytes(AdvancedLeakAwareByteBuf.java:496)
com.tutorgroup.base.logserver.service.LogService.getLogJSONArray(LogService.java:108)
com.tutorgroup.base.logserver.service.LogService.process(LogService.java:51)
com.tutorgroup.base.logserver.server.UDPInboundHandler.channelRead0(UDPInboundHandler.java:25)
com.tutorgroup.base.logserver.server.UDPInboundHandler.channelRead0(UDPInboundHandler.java:13)
io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:105)
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434)
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965)
io.netty.channel.nio.AbstractNioMessageChannel$NioMessageUnsafe.read(AbstractNioMessageChannel.java:93)
io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:644)
io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:579)
io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:496)
io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:458)
io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
java.lang.Thread.run(Thread.java:748)
Created at:
io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:331)
io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:185)
io.netty.buffer.UnsafeByteBufUtil.copy(UnsafeByteBufUtil.java:436)
io.netty.buffer.UnpooledUnsafeDirectByteBuf.copy(UnpooledUnsafeDirectByteBuf.java:463)
io.netty.buffer.AbstractByteBuf.copy(AbstractByteBuf.java:1190)
io.netty.channel.socket.DatagramPacket.copy(DatagramPacket.java:47)
com.tutorgroup.base.logserver.server.UDPInboundHandler.channelRead0(UDPInboundHandler.java:24)
com.tutorgroup.base.logserver.server.UDPInboundHandler.channelRead0(UDPInboundHandler.java:13)
io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:105)
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434)
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965)
io.netty.channel.nio.AbstractNioMessageChannel$NioMessageUnsafe.read(AbstractNioMessageChannel.java:93)
io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:644)
io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:579)
io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:496)
io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:458)
io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
java.lang.Thread.run(Thread.java:748)
查阅相关资料,释放ByteBuf 方式,修改代码如下
@Component
public class UDPInboundHandler extends SimpleChannelInboundHandler<DatagramPacket> { private Logger logger = LoggerFactory.getLogger(UDPInboundHandler.class); @Autowired
LogService logService; @Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) {
String remoteAddr = packet.sender().getAddress().getHostAddress();
ByteBuf buf = packet.copy().content();
try{
logService.process(buf, remoteAddr);
buf.clear();
}catch (Exception e){
logger.error(e.getMessage(),e);
}
finally {
ReferenceCountUtil.release(buf);
}
}
}
ReferenceCountUtil.release() 是netty释放堆外内存的方法,加上这行代码后,问题完美解决。
参考资料:
http://static.muyus.com/html/3.html
https://www.jianshu.com/p/17e72bb01bf1
记录一次OOM排查经历的更多相关文章
- 记录一次OOM排查经历(一)
一.经历概要 程序里有个跑数据的job,这个job的主要功能是往数据库写假数据. 既需要跑历史数据(传给job的日期是过去的时间),也需要能够上线后,实时跑(十秒钟触发一次,传入触发时的当前时间). ...
- 【转】又一次线上 OOM 排查经过
又一次线上OOM排查经过 最近线上一个服务又出现了频繁Full GC的情况,导致提供的业务经常超时.问题出现非常不稳定,经过两周的时候,终于又捕捉到了一次Full GC,于是联系运维做Heap Dum ...
- 华为云数据库GaussDB(for Cassandra)揭秘第二期:内存异常增长的排查经历
摘要:华为云数据库GaussDB(for Cassandra) 是一款基于计算存储分离架构,兼容Cassandra生态的云原生NoSQL数据库:它依靠共享存储池实现了强一致,保证数据的安全可靠. 本文 ...
- 实战经验 | Cassandra Java堆外内存排查经历全记录
背景 最近准备上线cassandra这个产品,同事在做一些小规格ECS(8G)的压测.压测时候比较容易触发OOM Killer,把cassandra进程干掉.问题是8G这个规格我配置的heap(Xmx ...
- 超干货!Cassandra Java堆外内存排查经历全记录
背景 最近准备上线cassandra这个产品,同事在做一些小规格ECS(8G)的压测.压测时候比较容易触发OOM Killer,把cassandra进程干掉.问题是8G这个规格我配置的heap(Xmx ...
- 记录一次elasticsearch-5.6.4宕机排查经历
犯罪现场~~ es: 三节点,配置相同 内存: 248G CPU: 没注意看 磁盘: 2T data: 380G左右 indices: 近9800条 在下才疏学浅,目前跟着大佬学习,这个问题还没解决, ...
- Java 性能优化实战记录(3)--JVM OOM的分析和原因追查
前言: C/C++的程序员渴望Java的自由, Java程序员期许C/C++的约束. 其实那里都是围城, 外面的人想进来, 里面的人想出去. 背景: 作为Java程序员, 除了享受垃圾回收机制带来的便 ...
- FastDFS并发会有bug,其实我也不太信?- 一次并发问题的排查经历
前一段时间,业务部门同事反馈在一次生产服务器升级之后,POS消费上传小票业务偶现异常,上传小票业务有重试机制,有些重试三次也不会成功,他们排查了一下没有找到原因,希望架构部帮忙解决. 公司使用的是Fa ...
- SQL Server死锁排查经历 -基于SqlProfiler
提到sql server,想必最让人头疼的当属锁机制了.在默认的read committed隔离模式下,连最基本的select操作都要申请各种粒度的锁,而且在读取数据过程中会不断有锁升级.转化.在非 ...
随机推荐
- Spring Boot中一个Servlet主动断开连接的方法
主动断开连接,从而返回结果给客户端,并且能够继续执行剩余代码. 对于一个HttpServletResponse类型的对象response来说,执行如下代码: response.getWriter(). ...
- Docker Swarm 集群(十七)
目录 一.Docker Swarm 概念 1.集群 2.Docker Swarm 3.重要概念 swarm node service 二.创建 Swarm 集群 1.环境准备 2.创建 swarm 3 ...
- Hyper-V VM Generation 2 - Can not boot from .ISO (Hyper-V Gen2不能從DVD啓動 ISO)
Q: Hyper-V VM Generation 2 - Can not boot from .ISO (Hyper-V Gen 2不能從DVD啓動 ISO ) A: Disable secure ...
- VLOOKUP函数 from Excel
1.VLOOKUP函数是Excel中的一个纵向查找函数,它与LOOKUP函数和HLOOKUP函数属于一类函数,在工作中都有广泛应用,例如可以用来核对数据,多个表格之间快速导入数据等函数功能.功能是按列 ...
- Linux系统终端常用配置文件更改
目录列表: 1.alias别名永久保存 2.解决vim文件没有颜色的问题 3.vim插件supertap插件安装(可支持自动补全,非函数代码补全,仅支持在当前编辑文档内补全) 4.vim插件管理 5. ...
- redis的主从复制和哨兵模式
Redis主从复制是什么? 行话:也就是我们所说的主从复制,主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主 Redis主从复制 ...
- 1、3 list 加入缓存,并postman测试
1.pom.xml导入redis依赖 2.配置文件配置 redis 3.主类开启缓存注解 4.Service方法上加注解 @Cacheable(value="list")//val ...
- win10系统不能ping通vmware虚假机解决办法
在使用vmware安装虚拟机后,在虚拟机里面可以上网,但就是不能在宿主机通过远程连接工具连接 同时也不能ping通虚拟机 检查网络配置 查看网关 在宿主机打开 ip地址填写刚刚查看的网关,同时把子网掩 ...
- 深度学习-mnist手写体识别
mnist手写体识别 Mnist数据集可以从官网下载,网址: http://yann.lecun.com/exdb/mnist/ 下载下来的数据集被分成两部分:55000行的训练数据集(mnist.t ...
- spring cloud微服务实践五
本篇我们来看看怎么实现spring cloud的配置中心. 在分布式系统中,特别是微服务架构下,可能会存在许多的服务,每个服务都会存在一个或多个的配置文件.那怎么多的配置文件的管理就会成为一个大问题. ...