Skip to main content
  1. posts/

Java堆外内存排查

·789 words·2 mins

启动参数

java -XX:NativeMemoryTracking=detail -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+AlwaysPreTouch -XX:ReservedCodeCacheSize=128m -XX:InitialCodeCacheSize=128m -Xss512k -Xmx1g -Xms1g -XX:+UseG1GC -XX:G1HeapRegionSize=4M -jar lib/helfy-1.0-SNAPSHOT.jar

现象

程序启动一段时间,就会被自动kill掉。

image.png
image.png
image.png

分析

从上面的jstat -gcutil 现象来看,应该不是heap OOM,也不是 Metaspace OOM。 那可能是堆外内存OOM,怎么验证呢? 通过top分析Java进程内存占用

image.png
image.png
当这个内存到了3.7g多的时候,程序就会被kill掉。 通过jmap -heap 104139 查看Java堆内存占用
image.png
我们也可以通过pmap来查看进程的内存占用。 pmap -x 104139 | tail -n 1
image.png
通过jmap和pmap可以发现,pmap的内存占用比jmap的堆配置总和还多。 而且pmap的RSS也一直在涨。

解释一下这个RSS的含义,如下图。一般来讲共享库所占的内存应该不会很多

image.png
https://www.jianshu.com/p/3bab26d25d2e

这里基本就能确定是堆外内存OOM了

默认来讲,堆外内存大小=设置的堆内存大小-XX:Xmx,但是从top的结果来看,基本上在3.7多就被kill了。 堆内存设置的是1g,然后metaspace设置的好像是256m。那如果堆外内存是1g的话,感觉说不太通。 ​

为什么会发生堆外内存OOM呢?看看线程到底在做什么 先top一把 top -H 打印所有线程的使用情况

image.png

这个 280376一直是用了88%的cpu,稳居榜首。 通过jstack看看它到底在干嘛(这里存在一个十进制转16进制的转换) jstack 280375 | grep ‘44738’ -A 40

image.png
从业务代码可以看出,应该是这个one.helfy.SleepyBean里面一直调用springboot的方式去解压jar包。用的是Inflater这个类,一直在申请堆外内存。

祖传命令

# 观察GC情况
ps -afx | grep java  | grep -v grep | awk '{print $1}' | xargs -I{} jstat -gcutil {} 1s

# 从JVM角度观察内存
jcmd <pid> VM.native_memory detail

# 观察内存使用情况
pmap -x 1

# 排序一下内存占用
pmap -x 1 | sort -n -k3

# 跟踪系统调用(看谁在分配内存)
strace -f -e 'brk,mmap,munmap' -p <pid>

总结

Top + jstack 分析cpu高负载场景 假设Java进程pid=60

  1. top -H 60 ,找到一个pid=280376的高负载线程
  2. printf %x 280376 | xargs -I {} sh -C “jstack 60 | grep {} -A 40”

参考

https://tech.meituan.com/2019/01/03/spring-boot-native-memory-leak.html (案例来源) https://tech.meituan.com/2020/11/12/java-9-cms-gc.html (处理思路: 场景九)