生产实践——内存溢出(OOM)问题排查

本文将模拟一个内存溢出环境,重现生产服务器排查过程。

一、环境搭建

  1. 使用SpringBoot应用进行模拟,代码如下:
...
@RequestMapping("/oom/creation")
public void createOOM() {
    List<OOMObject> oomList = new ArrayList<>();
    for (;;){
        oomList.add(new OOMObject());
    }
}
复制代码
  1. 接下来,我们在服务器上进行部署,为避免影响主机上其他服务,这里我们设置最大堆内存为128MB

nohup java -jar -Dserver.port=8083 -Xms128m -Xmx128m springboot-web-demo-1.0-SNAPSHOT.jar &

注意这里需要已经安装JDK,如果未安装,请参考:Linux安装OpenJDK.

3.最后我们检查下应用启动状态,执行:

jps

b0a7d18eb4378d7047513f005543d53.png

ps -ef | grep java | grep -v grep

3ca45656ed157204874da4d03b5ff1d.png

可以看到启动了一个进程号为16327的应用。

二、模拟内存溢出

模拟前,我们先通过top -Hp 16327(pid)看下该进程CPU使用情况:

-H 线程模式
-p 指定进程ID
复制代码

e996316f413a43e9478b4e2ef795991.png

然后调用之前定义好的接口,进行模拟:

1e3c4252a4e2ff12154da7d3fa2b305.png 也可以直接通过浏览器访问或者使用curl命令调用。调用之后再使用top -Hp 16327(pid)查询进程CPU使用情况如下:

8d062f2f1162c76b182f6041dc63496.png 这里我们看到内存占用在几秒钟就飙升到82.5%。

三、问题排查

接下来我们通过jstack查看PID的16330的线程,这里我们先把PID转为16进制:

printf "x%\n" 16330

100f4e3a4c0cb2895ee6880b536fcaa.png

再通过jstack命令查看该线程:

jstack -l 16327 | grep -20 3fca

39453fdaa1448f4e74695f4640b803d.png

我们发现该线程为GC线程,接下来通过jmap查询GC情况,我们这里直接看堆内存的对象情况:

jmap -histo 16327 | head -n 10

c366b584cd5fe9a159c5723157c3690.png 可以看到OOMObejct对象创建了400多万个实例,明显异常,我们通过搜索代码OOMObject对象的usages,发现该代码在:

...
@RequestMapping("/oom/creation")
public void createOOM() {
    List<OOMObject> oomList = new ArrayList<>();
    for (;;){
        oomList.add(new OOMObject());
    }
}
复制代码

这里有一个死循环,至此问题排查到,修改后重新上线。

四、其他排查方式

因为线上服务器安全问题,我们这里使用的是JDK、Linux提供的原生命令进行排查,其他方式参考:

  • 通过JConsolejvisualvm分析dump日志
  • 通过arthas排查

这里我们演示下arthas:

1.启动arthas

wget https://alibaba.github.io/arthas/arthas-boot.jar 
java -jar arthas-boot.jar
复制代码

2.我们这里简单演示下dashboard命令:

e395857be47a44737d9274261650af6.png

可以看到GC线程的内存率较高。其他方式具体请查看参考资料

五、参考资料

  1. arthas
  2. JConsole
  3. JVisualVM
  4. top
  5. jmap

猜你喜欢

转载自juejin.im/post/7085326528560496653