事件背景
生产环境上,有一个应用部署的容器上,总是一两天自动重启一次,怀疑应该是内存溢出/泄漏导致。
排查过程分析
1、首先从pinpoint 监控上查看JVM的内存信息
如上图所示,Java应用设置的堆内存为1.2G,metaspace 256M,在应用重启之前,此时heap的内存为800m左右,非堆内存占用也非常的稳定。此时从监控数据看,可以排除是应用程序本身发生内存溢出,而且重启的时候也没有dump文件的生成。
2、此时来看K8S容器的监控
如上图所示,重启时容器内的内存已经到达了100%,然后去看容器的日志,确实是在应用申请内存时显示内存不足,然后触发了OOM ,此时容器发起了应用评分,然后把Java应用kill掉了。到此时应该可以判定,应该该应用有内存泄漏引起的,并且是堆外内存。
3、添加NMT追踪内存 NMT(Native Memory tracking)是一种Java HotSpot VM功能,可跟踪Java HotSpot VM的内部内存使用情况(jdk8+)。 使用方法: 开启
在JVM启动参数中添加-XX:NativeMemoryTracking=detail
查看 jcmd 进程id VM.native_memory summary scale=MB
然后重启项目,观察容器内存监控,当容器内存监控到达98% 时,通过jcmd 命令查看内存追踪
Native Memory Tracking:
Total: reserved=6065MB +2914MB, committed=4917MB +2961MB
- Java Heap (reserved=1228MB, committed=1228MB)
(mmap: reserved=1228MB, committed=1228MB)
- Class (reserved=1192MB +32MB, committed=188MB +36MB)
(classes #28148 +4283)
(malloc=6MB +2MB #77459 +30082)
(mmap: reserved=1186MB +30MB, committed=182MB +34MB)
- Thread (reserved=3141MB +2794MB, committed=3141MB +2794MB)
(thread #6173 +5497)
(stack: reserved=3114MB +2770MB, committed=3114MB +2770MB)
(malloc=20MB +18MB #30865 +27485)
(arena=7MB +6 #12343 +10994)
- Code (reserved=266MB +9MB, committed=121MB +53MB)
(malloc=22MB +9MB #29403 +11126)
(mmap: reserved=244MB, committed=99MB +43MB)
- GC (reserved=9MB, committed=9MB)
(malloc=5MB #2822 +1372)
(mmap: reserved=4MB, committed=4MB)
- Compiler (reserved=3MB +2MB, committed=3MB +2MB)
(malloc=3MB +2MB #4822 +2882)
- Internal (reserved=145MB +71MB, committed=145MB +71MB)
(malloc=145MB +71MB #117204 +76609)
- Symbol (reserved=29MB +1MB, committed=29MB +1MB)
(malloc=26MB +1MB #290132 +15483)
(arena=3MB #1)
- Native Memory Tracking (reserved=10MB +4MB, committed=10MB +4MB)
(malloc=1MB +1MB #15058 +9675)
(tracking overhead=9MB +3MB)
- Unknown (reserved=42MB, committed=42MB)
(mmap: reserved=42MB, committed=42MB)
可以看到,thread 的内存比启动时申请的内存多了巨多。 这时可以判断为 是应用程序的线程堆栈泄漏了,现在就是要来找到是哪个线程引起的。
4、启动容器的arthas ,使用命令 thread -n 20 把占用最多的前20线程的堆栈信息打印出来,就信息中就找到了内存泄漏的可疑线程。
最后附上一张JVM内存划分图: