JDK8环境G1收集器220GB大堆出现OOME问题的排查问询
排查建议
1. 确认当前使用的垃圾回收器配置
- JDK 8如果启用了G1回收器,大对象(大小超过Region大小的一半)会被直接分配到老年代的Humongous区域,这个区域的回收逻辑在JDK 8 u292及之前版本存在较多缺陷:
- 若没有显示设置
-XX:G1HeapRegionSize,220G堆默认的Region大小会是128M,意味着超过64M的对象都会被判定为大对象 - JDK 8的G1只有在并发标记周期或者Full GC的时候才会回收Humongous对象,如果老年代增长过程中没有触发对应的回收阶段,就会出现明明堆还有空余,但Humongous区域占满老年代可用空间触发OOM的情况
- 若没有显示设置
- 执行命令
jinfo -flag UseG1GC <pid>确认是否启用G1,jinfo -flag G1HeapRegionSize <pid>查看当前Region大小
2. 检查老年代空间动态调整逻辑
- 由于未预设新生代、老年代大小,Parallel GC/G1都会动态调整代际空间占比:
- 如果短时间内大量大对象直接进入老年代,会触发JVM动态扩大老年代的阈值,但扩代逻辑需要等到GC safepoint才能执行,如果大对象分配速度快于扩代速度,就算总堆还有剩余,当前老年代的可用空间不够分配大对象也会直接OOM
- 排查GC日志里的
[Ergo相关日志,确认是否有老年代扩容失败、或者动态调整代际占比的记录
3. 排查内存碎片问题
- 大对象分配需要连续的内存空间:
- 如果使用的是CMS回收器,老年代采用标记清除算法,长时间运行后会产生大量内存碎片,就算老年代总剩余空间远大于要分配的大对象大小,也会因为没有足够的连续空间触发OOM
- 排查GC日志里的Full GC记录,看Full GC之后老年代的可用连续空间大小,如果Full GC后总可用空间足够但还是分配失败,基本可以确定是内存碎片问题
4. 验证大对象的生命周期与引用持有情况
- 你提到老年代几乎没有被回收,先确认这些大对象是否被不必要的长生命周期引用持有:
- 业务低峰期执行命令
jmap -dump:live,format=b,file=heap.bin <pid>导出存活对象堆快照(注意220G堆dump会占用大量磁盘IO,需预留足够磁盘空间) - 用MAT等工具分析快照,确认大对象的引用链,排查是否有内存泄漏导致大对象无法被回收
- 业务低峰期执行命令
临时优化方案(无需升级JDK)
- 若确认使用G1回收器:
- 显示设置
-XX:G1HeapRegionSize调整Region大小,减少大对象判定数量 - 增加
-XX:+AlwaysPreTouch参数,避免运行时才申请物理内存导致分配阻塞 - 调整
-XX:InitiatingHeapOccupancyPercent(默认45)降低并发标记触发阈值,提前回收Humongous对象
- 显示设置
- 若使用CMS回收器:
- 增加
-XX:+UseCMSCompactAtFullCollection和-XX:CMSFullGCsBeforeCompaction=0参数,每次Full GC都对老年代做内存压缩消除碎片 - 固定新生代和老年代的大小,避免动态扩代导致的分配失败
- 增加
内容的提问来源于stack exchange,提问作者Erik
相关产品推荐
相关产品推荐

