一次线上OOM(java.lang.OutOfMemoryError: GC overhead limit exceeded)

环境

  • 16G运行内存,PostgreSql数据库
  • Java8默认收集器Parallel Scavenge, 即新生代 PS-Scavenge, 老年代 PS-MarkSweep(Parallel-Scavenge收集器架构中本身的老年代收集器,与Serial Old的实现非常接近)

原因

  • 有一个定时任务, 在应用启动的时候运行,由于查询时间范围较大, 数据量13w多(大小50-100k/每条 )

排查

  • 发现问题时是因为服务器负载高, 处理请求慢, 部分接口返回504
  • show-busy-java-threads.sh 使用此脚本来查看消耗量最高的几个线程, 发现几个线程都是GC线程,也就是应用在不断的GC;(https://github.com/oldratlee/useful-scripts/blob/dev-2.x/bin/show-busy-java-threads)可下载此脚本
  • jstat -gc 6533 5000 来查看具体使用情况,发现应用启动时不断进行FGC,当服务器运行一段时间后(也就是有问题的task执行结束)逐渐趋于稳定
    一次线上OOM(java.lang.OutOfMemoryError: GC overhead limit exceeded)
  • java.lang.OutOfMemoryError: GC overhead limit exceeded:查看到错误输出信息, 这个原因是用了98%的时间回收了不到2%的内存空间, 是OOM的一个“预警”, 机器实际并未宕掉;
  • 到这里基本能确定不断的FGC导致服务不稳定, 查看系统参数-Xms:2048m Xmx:4096m,可见堆分配空间不算小, 大概率是代码方面的问题
  • -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/… 程序启动时有设置这个参数 那就可以直接将dump文件下载来分析了
  • scp [email protected]:/opt/xxx/var/java_pid3074.hprof ~/Desktop 使用scp工具来下载到桌面

MAT工具分析

  • 从dump文件看到有两块内容耗尽3G左右内存
    一次线上OOM(java.lang.OutOfMemoryError: GC overhead limit exceeded)
  • 在Overview下方选择Dominator tree查看这两块具体情况
    一次线上OOM(java.lang.OutOfMemoryError: GC overhead limit exceeded)
    可以发现查询数据库时返回的结果存在ResultSet集合中过多, 同时在线程-10对此结果进行反序列化封装封装成新对象,两个步骤导致OOM.
    一次线上OOM(java.lang.OutOfMemoryError: GC overhead limit exceeded)
    一次线上OOM(java.lang.OutOfMemoryError: GC overhead limit exceeded)
    继续:
    一次线上OOM(java.lang.OutOfMemoryError: GC overhead limit exceeded)
    一次线上OOM(java.lang.OutOfMemoryError: GC overhead limit exceeded)
    这里便能发现具体有问题的task,然后正确的处理即可。

触发FGC

  • 这个大对象何时触发FGC?通过jstat -gc pid 5000 查看到内存信息,应用程序启动时刚开始并没有YGC而直接触发了FGC,可推断大对象直接分配到了老年代
  • 另外Metaspace元空间较小也可能触发FGC,设置参数-XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=768m后发现仍然没有效果, 通过stat -gc pid 5000看元空间使用并未达到512m,因此不会触发FGC

小结

  • OOM的原因有两方面,一是系统参数设置较小, 调整参数即可解决,二是代码层面的问题, 死循环 对象太大等原因
  • 确定线上问题是OOM的方式很多,查看输出日志文件, jstat -gc pid time 查看内存信息以及show-busy-java-theads.sh等等;
  • 确定OOM之后检查系统参数是否合理;确定系统参数没有问题之后 基本就是代码的问题了;
  • 检查最近提交的代码是否可能导致问题-大对象 死循环等;
  • 如果最后还是发现不了的话就下载并分析dump文件

以上是个人理解,如有问题请指出 谢谢!