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
javac test.java
java -Xms10m -Xmx10m -XX:+PrintGC -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./ Test 2
注:
-Xms10m -Xmx10m 初始堆大小和最大堆大小。 这里是模拟,所以设置小一点
-XX:+PrintGC -XX:+PrintGCDetails 打印一下GC的日志
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./ 当发送OOM的时候,自动保存Dump文件。后面那个是报错的路径,如果没有则,在当前目录下。
已运行会一直打gc日志,因为死循环在添加对象,并且对象还没法回收。
一会就会内存溢出,如下图
4分析dump文件
发生OOM了。接着就是要分析一下这个dump文件了。分析的工具有很多,这里使用mat和jvisualvm分别分析一下,其实都差不多。
首先,从服务器下载到本地。然后使用工具就行分析。
1.首先使用MAT分析:
打开mat,导入hprof文件。(注:如果hprof文件特别的大,需要调整一下mat的内存参数。防止内存溢出,打不开文件)
mat
我们可以看到mat已经帮我们分析到了一个错误:
点击details详情。会发现:
线程 OOMThread中一个ArrayList占了98.12%,而它里面放的是String对象。
注:这里面有两个比较难理解的名词(我理解的不一定完全对。大家可以自行百度一下):
1.shallow heap:shallow:浅的,我理解就是:这个对象实际占用的堆大小
2.retained heap:retained:保留的,这个有点难理解了:简单的话,就是它和它引用的对象(这里的引用对象没有被其他的对象引用)的占用堆的大小。(不知道理解的对不对。)如下图:arrayList中shallow是24,retained是9057600.以为我newString的都放到list中去了。
初步怀疑是ArrayList中对象过多,一直有引用,没有被gc回收导致。然后看一下抛出OOM的异常的地方。发现有对应的ArrayList。它有一个循环在一直添加String对象。
2.使用jvisualvm.exe
打开jvisualvm.exe,导入hprof文件。
在出现 OutOfMemoryError 异常错误时进行了堆转储导致 OutOfMemoryError 异常错误的线程: OOMThread
点击:OOMThread
显示是在什么地方发生的OOM,然后点击 红框的地方。
我们可以发现:arrayList的size大小为:317374. 猜测是arrayList结合中对象太多。由此可以去查对应的代码逻辑判断。
还可以点击类:会显示对象的个数和占比。会发现string对象,有318309个实例,占所有实例中99.1%,大小是8912652,占用了堆74.2%的空间
注:实际生产中情况,比这个要复杂,不过大概分析过程就是这样。其实频繁的fullgc也可以导出dump文件去分析。是不是有内存泄漏的地方。当然,一般大公司会有相应的监控系统,在堆空间超过某个阈值就会报警,这个时候,可以将这台机器从vip摘除或者(healthcheck先摘除了)这样Nginx就打不过来,就不会有用户的请求过来了。然后使用jmap导出dump文件分析。这个以后有时间再讲。