一次Java直接内存溢出定位经历

环境:JDK8

现象:

测试压测过程中,有一个转发服务容器的内存占比98%,过一段时间后,容器重启;问题可复现

定位:

容器有重启行为,可以使用dmesg 先查看系统错误日志

dmesg  -H | tail -n 50

发现:是宿主机 cgroup将容器进程kill掉

1.  是否是堆内存泄漏?

    首先看堆内存是不是泄漏有泄漏?

    当容器内存占用率达到90%左右时,通过jmap命令查看服务进程堆内存大小。发现堆内存属于正常范围内。排除堆内存溢出问题。

2. 是否堆外内存泄露

 给服务进程添加启动参数:-XX:NativeMemoryTracking=summary, 重启服务,用于开启Native Memory Tracking (NMT)特性;

注意:其中该值默认为off,可以设置为summary或者detail来开启;开启的话,大概会增加5%-10%的性能消耗。

在大概容器内存占用率在30%左右时,基于此时jvm运行状态,设定为baseline

./jcmd pid VM.native_memory  baseline

内存达到80%的时候jcmd生成的信息

./jcmd pid VM.native_memory summary.diff

可以看到heap大小不变,是堆的capacity。而native memory其他项都增加得不多,而此时的internal增加了约800M。

internal具体是什么含义还不清楚,不过确实能看到堆外内存在不断增加;

那是什么地方用到了堆外内存了呢?

我们的代码中并没有显示的申请堆外内存;应该是调用某些框架的时候,隐式的用到了堆外内存,大概率是Netty了,我们的代码中有使用Netty进行Stomp协议连接。

大概看了下Netty的代码,默认使用的就是DirectBuffer。

问题验证:

通过增加-Dio.netty.maxDirectMemory参数限制Netty可以使用的directMemory上限

当directMemory达到最大值时,在日志中看到报了OutOfDirectMemoryError,并且定位到了是PlatformDependent.incrementMemoryCounter时因为内存不足报的错;符合预期;基本上就是Netty相关问题。

问题代码定位:

多次细心review代码中和Netty相关的部分,发现当客户端建立连接后,服务端会记录客户端,当有新的消息需要广播的时候,会轮询客户端列表,创建对应的消息帧(DirectBuffer)进行发送;

而客户端断链时,服务端并没有删除该客户端,导致给很多无效的客户端,创建了消息帧(DirectBuffer),导致Netty使用的堆外内存超出限制,在设置了-Dio.netty.maxDirectMemory参数的情况下会报OutOfDirectMemoryError;在没有设置-Dio.netty.maxDirectMemory参数的时候,会一直使用堆外内存,直到超过容器设置的最大内存,被宿主机kill掉。

参考:docker - Why is Internal memory in java Native Memory Tracking increasing - Stack Overflow

spring boot - Java Heap Dump : How to find the objects/class that is taking memory by 1. io.netty.buffer.ByteBufUtil 2. byte[] array - Stack Overflow

通过对上面的分析,我们有一些问题需要解答

堆外内存溢出引发的思考
1.为什么要用堆外内存?
在IO操作中,使用堆外内存,而不是堆内内存,在数据传输的过程中可以减少堆内内存和堆外内存之间的相互拷贝。


2.堆外内存的回收
Java中堆外内存的回收,Netty中堆外内存的回收
扩展Cleaner
3.怎么限制堆外内存的大小
Netty怎么限制, java启动命令怎么限制
4.容器中启动java程序,怎么限制直接内存使用大小

通过这两个参数-XX:MaxDirectMemorySize和-Dio.netty.maxDirectMemory

参考:-XX:MaxDirectMemorySize和-Dio.netty.maxDirectMemory区别-CSDN博客

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值