
现象分析
服务运营过程中,内存持续上升并未释放,超过设定的阈值并触发了告警。观察发现内存占用超过JVM参数`-Xmx10G`的限制,达到了14G。
排查过程
1. 初步观察与确认:
通过查看容器内存监控,发现内存确实在持续增长且不释放。
使用`top`命令确认java进程确实占用了14G物理内存。
2. 查看JVM启动参数:当前参数表明系统设定了最大堆内存为10G。结合现象,初步判断是堆外内存泄漏。
3. 深入调查:
为了确定内存的具体使用情况,决定Native Memory,在JVM启动参数中加入`-XX:NativeMemoryTracking=detail`来详细内存分配情况。
使用`jcmd`命令查看Native内存情况,发现两部分存在问题:线程数量过多和Internal部分内存占用过大。
4. 线程数量分析:
每个线程的stack默认是1M,但实际需要可能会增长。计算结果显示线程占用内存与观察到的数据基本吻合。进一步使用`jstack`命令分析线程类型,发现NettyClientPublicExecutor线程过多,其次是DubboServerHandler线程。进一步分析代码发现NettyClientPublicExecutor线程数量与cpu核数有关,但在容器环境下可能存在误解读宿主机的核数而非容器的核数问题。经过验证,使用的jdk版本存在获取处理器数量的bug。升级jdk版本后解决了这个问题。
5. Internal内存占用分析:这部分内存分配难以追踪,初步猜测可能与opencv库有关,该库通过native方法分配了内存但并未主动释放。代码中存在使用opencv进行人脸检测的部分,并且观察到相关的native对象没有主动销毁,只在finalize方法中尝试释放堆外内存。尝试主动释放后看到内存有所下降但仍存在增长趋势。
6. 进一步排查:观察到容器内存持续缓慢增长,但JVM的native memory增长量较小。使用`jcmd`对比前后的内存差异确认是JVM外的堆外内存泄漏。尝试使用`pmap`命令查看进程的内存映射以确定是否存在固定大小的内存泄漏如常见的64M问题,但实际情况并非如此。观察到某些固定大小的内存块增长明显,通过gdb尝试dump此部分内存内容却发现都是乱码,排查陷入僵局。计划后续使用gperftools进行进一步分析。
结论与展望
此次现象指向堆外内存泄漏,涉及到多个方面如JVM内部的线程问题、特定库的内存管理等。虽然排查过程中遇到了一些困难如无法追踪的堆外内存泄漏等,但已经采取了一些措施并取得了一定效果。后续将继续问题并寻求解决方案。
