一次线上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执行结束)逐渐趋于稳定
- 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左右内存
- 在Overview下方选择Dominator tree查看这两块具体情况
可以发现查询数据库时返回的结果存在ResultSet集合中过多, 同时在线程-10对此结果进行反序列化封装封装成新对象,两个步骤导致OOM.
继续:
这里便能发现具体有问题的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文件
以上是个人理解,如有问题请指出 谢谢!