1.引子

今天聊一下OOM的问题。OOM就是Out Of Memory。前几天,线上出现过一次,频繁的full GC的问题。今天就简单记录一下排查的步骤。

2.模拟

在这只能模拟OOM。很简单,就是一个集合,写个循环向里面添加对象。这个方法:startThread2

import java.util.ArrayList;import java.util.List;class Test {    public static void main(String[] args) {        if (args.length == 0) {            System.out.println("运行的时候请输入参:1 or 2");            return;        }        if (args[0].equalsIgnoreCase("1")) {            startThread1();        }        if (args[0].equalsIgnoreCase("2")) {            startThread2();        }    }    public static void startThread1() {        Thread thread = new Thread(new Runnable() {            @Override            public void run() {                while (true) {                    String a = new String("deadThread");//System.out.println(a);                }            }        });        thread.setName("deadThread");        thread.start();    }    public static void startThread2() {        Thread thread = new Thread(new Runnable() {            @Override            public void run() {                List list = new ArrayList<>();                while (true) {                    String a1 = new String("OOMThread");                    list.add(a1);                }            }        });        thread.setName("OOMThread");        thread.start();    }}

3.编译运行

javac test.java




Javacore排查案例 java oom排查思路_Javacore排查案例

javac test.java



java -Xms10m -Xmx10m -XX:+PrintGC -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./ Test 2




Javacore排查案例 java oom排查思路_Javacore排查案例_02


注:

-Xms10m -Xmx10m 初始堆大小和最大堆大小。 这里是模拟,所以设置小一点

-XX:+PrintGC -XX:+PrintGCDetails 打印一下GC的日志

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./ 当发送OOM的时候,自动保存Dump文件。后面那个是报错的路径,如果没有则,在当前目录下。

已运行会一直打gc日志,因为死循环在添加对象,并且对象还没法回收。

一会就会内存溢出,如下图


Javacore排查案例 java oom排查思路_java_03


4分析dump文件

发生OOM了。接着就是要分析一下这个dump文件了。分析的工具有很多,这里使用mat和jvisualvm分别分析一下,其实都差不多。


Javacore排查案例 java oom排查思路_Test_04


首先,从服务器下载到本地。然后使用工具就行分析。

1.首先使用MAT分析:

打开mat,导入hprof文件。(注:如果hprof文件特别的大,需要调整一下mat的内存参数。防止内存溢出,打不开文件)


Javacore排查案例 java oom排查思路_Javacore排查案例_05

mat


Javacore排查案例 java oom排查思路_List_06


Javacore排查案例 java oom排查思路_List_07


Javacore排查案例 java oom排查思路_List_08


我们可以看到mat已经帮我们分析到了一个错误:


Javacore排查案例 java oom排查思路_List_09


点击details详情。会发现:

线程 OOMThread中一个ArrayList占了98.12%,而它里面放的是String对象。


Javacore排查案例 java oom排查思路_循环上传导致oom_10


注:这里面有两个比较难理解的名词(我理解的不一定完全对。大家可以自行百度一下):

1.shallow heap:shallow:浅的,我理解就是:这个对象实际占用的堆大小

2.retained heap:retained:保留的,这个有点难理解了:简单的话,就是它和它引用的对象(这里的引用对象没有被其他的对象引用)的占用堆的大小。(不知道理解的对不对。)如下图:arrayList中shallow是24,retained是9057600.以为我newString的都放到list中去了。


Javacore排查案例 java oom排查思路_Javacore排查案例_11


初步怀疑是ArrayList中对象过多,一直有引用,没有被gc回收导致。然后看一下抛出OOM的异常的地方。发现有对应的ArrayList。它有一个循环在一直添加String对象。

2.使用jvisualvm.exe

打开jvisualvm.exe,导入hprof文件。


Javacore排查案例 java oom排查思路_List_12


在出现 OutOfMemoryError 异常错误时进行了堆转储导致 OutOfMemoryError 异常错误的线程: OOMThread


Javacore排查案例 java oom排查思路_Javacore排查案例_13


点击:OOMThread


Javacore排查案例 java oom排查思路_java_14


显示是在什么地方发生的OOM,然后点击 红框的地方。


Javacore排查案例 java oom排查思路_Test_15


我们可以发现:arrayList的size大小为:317374. 猜测是arrayList结合中对象太多。由此可以去查对应的代码逻辑判断。

还可以点击类:会显示对象的个数和占比。会发现string对象,有318309个实例,占所有实例中99.1%,大小是8912652,占用了堆74.2%的空间


Javacore排查案例 java oom排查思路_List_16


注:实际生产中情况,比这个要复杂,不过大概分析过程就是这样。其实频繁的fullgc也可以导出dump文件去分析。是不是有内存泄漏的地方。当然,一般大公司会有相应的监控系统,在堆空间超过某个阈值就会报警,这个时候,可以将这台机器从vip摘除或者(healthcheck先摘除了)这样Nginx就打不过来,就不会有用户的请求过来了。然后使用jmap导出dump文件分析。这个以后有时间再讲。