简述 G1 垃圾回收器和 OOM 问题的排查
最近又碰到的 oom 的问题,一直在尝试定位中,由于现实使用的 G1 的垃圾回收器。所以今天打算线上的排查历程和方案查询出来。
jvm 常用参数
1 | -Xmx1024m 最大堆内存 |
几个命令
在排查的过程中用到的下面几个命令
1 | jmap |
出现 OOM 的问题,一般情况下来说,都是堆上面内存分配的太多,且无法回收,导致 JVM 的内存溢出。
jps
查看 java 运行时的 pidjmap -heap pid
看下堆上各个区的占用的内存大小jmap -histo pid
可以查看对应的类型的大小,或者使用 dump 成一个文件进行分析在对堆上的类型对象进行分析的时候,发现堆上的内存大小和回收的基本正常,实际使用的内存是大于堆上的内存, 这个时候我就开始怀疑是堆外内存的泄露的问题。
使用
pmap -x pid | sort -n -k3
指令,看下占用内存的地址空间和大小- 前面的工具如果再无法定位问题的话,就只能使用
perf
命令,基本就两条语句 perf record -g -p pid
开启监控栈函数调用。运行一段时间后 Ctrl+C 结束,会生成一个文件 perf.data。执行
perf report -i perf.data
查看报告。根据查看的内容,定位到 zip 的内容,但是内容还是不大,也 review zip 相关代码,进行本地测试,均为发生 OOM 的现象。
前面的几个办法都无法定位问题,只能使用最笨的办法,打印直接内存的大小。具体的代码如下:
public BufferPoolMXBean getDirectBufferPoolMBean(){ return ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class) .stream() .filter(e -> e.getName().equals("direct")) .findFirst() .orElseThrow(null); } public JavaNioAccess.BufferPool getNioBufferPool(){ return SharedSecrets.getJavaNioAccess().getDirectBufferPool(); } /** * -XX:MaxDirectMemorySize=60M */ @Test public void testGetMaxDirectMemory(){ ByteBuffer.allocateDirect(25*1024*1024); System.out.println(Runtime.getRuntime().maxMemory() / 1024.0 / 1024.0); System.out.println(VM.maxDirectMemory() / 1024.0 / 1024.0); System.out.println(getDirectBufferPoolMBean().getTotalCapacity() / 1024.0 / 1024.0); System.out.println(getNioBufferPool().getTotalCapacity() / 1024.0 / 1024.0); }
G1 回收器的特点
G1 不同于其他的分代回收算法、G1将堆空间划分成了互相独立的区块。每块区域既有可能属于 Old 区、也有可能是 Y 区,且每类区域空间可以是不连续的(对比 CMS 的 O 区和 Y 区都必须是连续的)。
这种将O区划分成多块的理念源于:当并发后台线程寻找可回收的对象时、有些区块包含可回收的对象要比其他区块多很多。虽然在清理这些区块时G1仍然需要暂停应用线程、但可以用相对较少的时间优先回收包含垃圾最多区块。这也是为什么G1命名为Garbage First的原因:第一时间处理垃圾最多的区块。
所以在看到 Old 区发生了变化,但是没有发生 Full GC
。这个就是 G1 的 mixed 垃圾回收回收的。
这是一篇水文,随便写写。
[参考资料]
【深入理解G1垃圾收集器】
【Java堆外内存排查小结】
【聊聊jvm的-XX:MaxDirectMemorySize】