实战技巧:内存爆满(OOM)问题的诊断与解决
本文将模拟一个内存溢出环境,重现生产服务器排查过程。
一、环境搭建
- 使用SpringBoot应用进行模拟,代码如下:
...
@RequestMapping("/oom/creation")
public void createOOM() {
List<OOMObject> oomList = new ArrayList<>();
for (;;){
oomList.add(new OOMObject());
}
}
- 接下来,我们在服务器上进行部署,为避免影响主机上其他服务,这里我们设置最大堆内存为128MB:
nohup java -jar -Dserver.port=8083 -Xms128m -Xmx128m springboot-web-demo-1.0-SNAPSHOT.jar &
注意这里需要已经安装JDK
,如果未安装,请参考:Linux安装OpenJDK.
3.最后我们检查下应用启动状态,执行:
jps
或 ps -ef | grep java | grep -v grep
可以看到启动了一个进程号为16327的应用。
二、模拟内存溢出
模拟前,我们先通过top -Hp 16327(pid)
看下该进程CPU使用情况:
-H 线程模式
-p 指定进程ID
然后调用之前定义好的接口,进行模拟:
也可以直接通过浏览器访问或者使用curl命令调用。调用之后再使用top -Hp 16327(pid)
查询进程CPU使用情况如下:
这里我们看到内存占用在几秒钟就飙升到82.5%。
三、问题排查
接下来我们通过jstack
查看PID的16330的线程,这里我们先把PID转为16进制:
printf "x%\n" 16330
再通过jstack
命令查看该线程:
jstack -l 16327 | grep -20 3fca
我们发现该线程为GC线程,接下来通过jmap
查询GC情况,我们这里直接看堆内存的对象情况:
jmap -histo 16327 | head -n 10
可以看到OOMObejct对象创建了400多万个实例,明显异常,我们通过搜索代码OOMObject对象的usages,发现该代码在:
...
@RequestMapping("/oom/creation")
public void createOOM() {
List<OOMObject> oomList = new ArrayList<>();
for (;;){
oomList.add(new OOMObject());
}
}
这里有一个死循环,至此问题排查到,修改后重新上线。
四、其他排查方式
因为线上服务器安全问题,我们这里使用的是JDK、Linux提供的原生命令进行排查,其他方式参考:
- 通过JConsole、jvisualvm分析dump日志
- 通过arthas排查
这里我们演示下arthas:
1.启动arthas
wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar
2.我们这里简单演示下dashboard命令:
可以看到GC线程的内存率较高。其他方式具体请查看参考资料。
五、参考资料
- arthas
- JConsole
- JVisualVM
- top
- jmap