本文将模拟一个内存溢出环境,重现生产服务器排查过程。
## 一、环境搭建
1. 使用SpringBoot应用进行模拟,代码如下:
```
...
@RequestMapping("/oom/creation")
public void createOOM() {
List<OOMObject> oomList = new ArrayList<>();
for (;;){
oomList.add(new OOMObject());
}
}
```
2. 接下来,我们在服务器上进行部署,为避免影响主机上其他服务,这里我们设置最大堆内存为**128MB**:
`nohup java -jar -Dserver.port=8083 -Xms128m -Xmx128m springboot-web-demo-1.0-SNAPSHOT.jar &`
注意这里需要已经安装`JDK`,如果未安装,请参考:[Linux安装OpenJDK](https://www.jianshu.com/p/f1e549d533da).
3.最后我们检查下应用启动状态,执行:
`jps`
![b0a7d18eb4378d7047513f005543d53.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/00571671323a43a28d2d8d5592814d92~tplv-k3u1fbpfcp-watermark.image?)
或 `ps -ef | grep java | grep -v grep`
![3ca45656ed157204874da4d03b5ff1d.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a21669deb0b2445aa4729788ee57e7b5~tplv-k3u1fbpfcp-watermark.image?)
可以看到启动了一个进程号为**16327**的应用。
## 二、模拟内存溢出
模拟前,我们先通过`top -Hp 16327(pid)`看下该进程CPU使用情况:
```
-H 线程模式
-p 指定进程ID
```
![e996316f413a43e9478b4e2ef795991.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7539c08342314648b2fb85e8aff0a7f5~tplv-k3u1fbpfcp-watermark.image?)
然后调用之前定义好的接口,进行模拟:
![1e3c4252a4e2ff12154da7d3fa2b305.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bd71b4c508184bcd90fac6200ba42428~tplv-k3u1fbpfcp-watermark.image?)
也可以直接通过**浏览器访问**或者使用**curl命令调用**。调用之后再使用`top -Hp 16327(pid)`查询进程CPU使用情况如下:
![8d062f2f1162c76b182f6041dc63496.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bb25e3e8c7b64d808f508fc91bc01c5f~tplv-k3u1fbpfcp-watermark.image?)
这里我们看到内存占用在几秒钟就飙升到82.5%。
## 三、问题排查
接下来我们通过`jstack`查看PID的16330的线程,这里我们先把PID转为16进制:
`printf "x%\n" 16330`
![100f4e3a4c0cb2895ee6880b536fcaa.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/33399ad4cf1640e5ab6dd0c84543e955~tplv-k3u1fbpfcp-watermark.image?)
再通过`jstack`命令查看该线程:
`jstack -l 16327 | grep -20 3fca`
![39453fdaa1448f4e74695f4640b803d.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c04850dabcc442bbb15fa7fbce2a45a2~tplv-k3u1fbpfcp-watermark.image?)
我们发现该线程为GC线程,接下来通过`jmap`查询GC情况,我们这里直接看堆内存的对象情况:
`jmap -histo 16327 | head -n 10`
![c366b584cd5fe9a159c5723157c3690.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ce382dea221846c9a5ec92901487a5ae~tplv-k3u1fbpfcp-watermark.image?)
可以看到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命令:
![e395857be47a44737d9274261650af6.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5e6cf6929b334d29a01f5d10b3652490~tplv-k3u1fbpfcp-watermark.image?)
可以看到GC线程的内存率较高。其他方式具体请**查看参考资料**。
## 五、参考资料
1. [arthas](http://arthas.gitee.io/)
2. [JConsole](https://www.apiref.com/java11-zh/jdk.jconsole/module-summary.html)
3. [JVisualVM](https://cloud.tencent.com/developer/article/1833559)
4. [top](http://events.jianshu.io/p/d58638e765f4)
5. [jmap](https://www.lifengdi.com/archives/article/2082)