一次线上OOM事故,一个Java程序员的自我救赎

一次线上OOM事故,一个Java程序员的自我救赎

当JVM抛出OutOfMemoryError时,如何快速定位根因?

通过近期协助同事解决JVM OOM故障的实践,总结形成这套可复用的排查方法。

OOM问题背景

同事:

最近我们Java进程经常运行一段时间就突然cpu飙高,服务假死了,看日志也没有任何的保存信息,就是卡在cpu高的那个时间段就没有日志输出了报错信息

这种情况大概率是内存泄漏了吧

重启服务之后运行了一两天又是这样

首先从对话中,我们可以看到内存溢出呈现两种现象:

  • 据观察运行一段时间之后,CPU 飙高 ;
  • 据观察服务假死,表现出来日志没有任何输出,也就是说服务端服务后续无法正常运行,服务的接口无法调用,下游业务受到影响。

我的第一想法是:非常明显的 JVM 内存溢出表现 ,不过不知道是爆然性的内存增长,还是缓慢的内存增长。

排查思路

于是,我的排查思路:

在服务假死时,可以每隔一段时间 观察 top -p Pid (进程号) 看看应用的内存占用情况。

类似的效果见下图:

接下来,我让他通过 jstat -gcutil pid 2479538看看 gc 的频率 。1000:采样间隔时间(毫秒),这里是每秒采样一次

从上图示例中观察新生代 E 区和O老年代,有没有飙到100,若提示100,基本都满了 ,基本可以确定是海量大对象产生导致 JVM OOM 了。

我:

基本看情况是 内存的问题 老年代、新生代都满了 有查数据库操作吧?

同事:

定时任务比较多,会不会是这个原因 ?

通过与同事的沟通分析,定时任务的执行机制很可能是问题的关键所在,十有八九,就是后台那个定时执行的功能出了问题。

接下来,同事发了张那段时间的监控图:

这张图,如同一群消防员(G1收集器)在堆内存中疲于奔命,却始终无法扑灭内存泄漏的'火情'

我觉得内存大小还可以 ,一般情况下通过 jmap -heap pid 来查看,示例图如下:

分析到这里,基本上我得到了如下的结论:

1、要查看代码中是否有一次性查询海量对象的操作 ;

2、或者有什么公共的对象一直在使用,而忘记了释放;

3、16 G 对一般的小应用来讲是绰绰有余的,而且他们的应用非高并发场景,是内网系统。

最后,我建议观察在日志停的那个时刻到底做了哪些事情,那才是真正的血案现场


那到底是什么原因导致 JVM OOM 呢 ? 和我预期的基本一模一样:

我:

来 说说到底什么原因

同事:

是一个初级开发判断没写好,直接全表查询,把几千万的数据放在list 里面

哈哈 和预期中的差不多 就是这个的问题

SQL 语句类似下图,查询条件没有拼接好,导致全表扫描。

我们总结下,解决 JVM 内存溢出问题的排查流程:

1、分析事故血案现场(CPU、内存、日志);

2、通过 top -p Pid (进程号)分析进程资源占用,判断是爆炸性的内存增长,还是缓慢的内存增长。

3、 jstat -gcutil pid 看看 gc 的频率 ,可以分析是否有大对象产生以及 查看 GC 频率。

4、 jmap -heap pid 分析真实的 JVM 内存占用 ,确认是否真的内存分配得太小了。

5、 类似的事故发生当时到底做了什么,有没有出现类似于内存或者 CPU 占用呈现脉冲飙高样子。

6、 若有飙高的场景,分析彼时彼刻到底有哪些操作。

7、 若是缓慢增长,则考虑使用 MAT 结合排除法分析内存占用。

上面的流程是我解决过内存溢出的套路,虽然很糙,但很实用,比如曾经帮助艺龙支付团队解决过订单查询内存溢出问题、西南某航空公司用户中心内存溢出问题等等。

最后,我想说:一定要注意 where 1 = 1 哦 ! 真的出现太多次啦,若是对于小表查询还好,大表一定要加条件限制。

扩展

在这里还接受一个小众JVM参数 : metaSpace

从表象看是发生了OOM。发生OOM前肯定会发生Full GC

触发Full GC是否是因为metaSpace空间使用占比飙升引起的呢?但通过监控可以看到发生Full GC时metaSpace使用占比仅仅在75%左右,并没有达到设置的MaxMetaspaceSize=256m。在翻阅了《深入理解Java虚拟机》以及众多技术博客想去了解metaSpace扩容原理后还是没有找到对应的答案。这个时候不得不说大数据的神奇了,当我在各大技术论坛、网站搜索这类信息后,给我推了这篇JVM源码分析之Metaspace解密,让我了解到了MaxMetaspaceFreeRatio参数,这个参数意思是当metaSpace空间占比达到该阈值后就进行Full GC,默认为70% 。这就说明了为什么会在75%左右发生Full GC了。

相关推荐
FirstMrRight5 分钟前
自动挡线程池OOM最佳实践
java·后端
程序员清风17 分钟前
Redis Pipeline 和 MGET,如果报错了,他们的异常机制是什么样的?
java·后端·面试
审计侠43 分钟前
Go语言-初学者日记(四):包管理
开发语言·后端·golang
Aska_Lv1 小时前
Linux---jstat命令的作用
后端
Moonbit1 小时前
双周报Vol.69: C FFI 支持 borrow、新增.mbt.md测试与调试、WASM 后端支持extern type..
程序员
嘻嘻哈哈开森1 小时前
从零开始学习模型蒸馏
人工智能·后端
DataFunTalk2 小时前
大模型时代数据科学岗位的未来思考
前端·后端·算法
阮瑭雅2 小时前
Java语言的Web安全
开发语言·后端·golang
编程乐趣2 小时前
UnitOfWork:一个支持多数据库,工作单元模式、支持分布式事务以及支持 MySQL 多数据库/表分片的开源项目
后端