OutOfMemory Error如何排查

最近线上项目有开始出现oom类型错误,为了方便下次排查,所以对java项目如何发生oom应该如何处理步骤流程大致梳理一下,方便日后使用。

如何能快速查看到异常堆栈信息

任何java项目,在运行过程中可以通过命令来实时获取该项目的堆栈详细数据信息,同时也可以设置在发生OutOfMemory时自动生成dump文件来供我们本地分析问题。

运行时获取dump文件

  • 首先通过命令行找到当前运行项目在服务器上的pid,也就是进程号,获取方式很多种,如果你是linux系统,可以通过ps -f |grep xxx.jar 来定位,或者通过jps来查看。

  • 获取到pid之后,就可以通过jmap命令来进行导出堆文件的导出。示例:

1
jmap -dump:file=javaDump.dump,format=b 24552

这样会获取到一个名为javaDump.dump的文件。

发生oom异常时自动生成dump文件

这个可以通过jvm启动参数来进行获取。

1
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./

如上,会在发生oom时,将dump文件输出到根目录下。

如何分析dump文件

dump文件搞定了,接下来是如何分析呢?如何通过工具或者别的方式来对dump出来的文件进行一个深入解读?

jhat分析

jdk中有自带jhat工具来对文件进行在线分析,通过jhat xxx.dump/hprof,之后,本地访问http://localhost:7000,即可在浏览器看到访问效果,如图:

通过jhat启动查看dump

jhat页面1

jhat页面2

jhat页面3

页面大致是长这个样子,重点查看 Other Queries目录下的SHow heap histogram,可以比较容易观察到对象的引用次数及占用内存的大小。

使用jhat优点是非常方便,通过命令行即可查看视图,缺点是如果文件过大,容易导致浏览器卡崩,而且都是通过纯文本描述,阅览上并不够直观。

jvisualvm分析

jdk的另一个工具,visualvm也可以对dump文件进行可视化分析,且功能比较强大,可以定位到具体运行的线程错误(代码行),虽然分析jvm内存是一件非常复杂工作,即使定位到代码行也不一定就是该位置导致的oom,但是和jhat相比,jvisualvm有很大优势。

找到所在操作系统内的$JAVA_HOME/bin/jvisualvm.exe,双击即可启动,启动后左上角文件->装入,选择指定的要分析的文件即可。

jvisualvm界面图

综合来看还是使用jvisualvm来进行dump文件的查询分析更直观、更利于定位问题。

测试案例

本地编写一块测试代码验证一下流程,方便记忆。

1
2
3
4
5
6
7
8
9
10
public class Test {

public static void main(String[] args) throws IOException {
List<String> list = new ArrayList<>();
int cnt = 0;
for (; ;) {
list.add(new String("i am couter this is " + cnt));
}
}
}

编写如上代码,且设置一下jvm启动参数:-Xmx20m -Xms20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./,在IDEA内设置参数非常方便:

IDEA设置jvm启动参数

IDEA设置jvm启动参数

这里需要注意,在测试时,不要设置-Xms太小,如果太小会在启动时报错:

1
2
Error occurred during initialization of VM 
GC triggered before VM initialization comA1eted. Try increasing NewSize, current value 1536K .

设置完之后启动main方法,在发生oom之后会自动导出dump文件到当前目录下。

oom控制台信息

oom文件

将这个文件通过分析工具来定位查看信息,通过[概要]项可以定位哪个该线程触发了错误信息,且户定位到代码行。

jvva visual VM page 1

通过[类]项可以更清晰的看到对象引用记录。

jvva visual VM page 2

可以看到引用最多的是char数组,其实就是string,因为string的底层结构就是char[],双击char[]进去之后,可以详细看到究竟是谁在引用它。

jvva visual VM page 3

可以看到,引用该类实例的是ArrayList持续装载,且可以定位到具体文本内容。

扩展补充 OutOfMemoryError的几种类型

  • OutOfMemoryError: GC overhead limit exceeded

    这是指程序基本耗尽了所有的内存 GC都清理不了,意味着GC占用了大部分CPU周期,大多数意味着98%或99%,并且在每次运行中它释放的内存非常少1-2%对别的线程影响很大影响整个系统吞吐量,比如一个线程占用98%内存无法释放,其他线程执行过程中由于内存限制就会抛出这个错误,甚至会导致连接数据库等这种操作变的缓慢,出现类似一个请求处理好几分钟的现象。

  • OutOfMemoryError: Java heap space

    相当于将xl型号的东西往只有s号空间内塞的意思,意味着应用程序在某些处理期间内存不足。

  • OutOfMemoryError: Permgen space | OutOfMemoryError: Metaspace

    在java8之前存在用永久代来实现方法区,这个永久代归堆管理,但是java8之后,将方法区的实现放在元空间,那么方法区内存不足时,在java8之前会出现:permgen space的oom错误,java8之后,也就是方法区通过元空间来实现,这个区域内存不足时会报:Metaspace

总结

在排查oom问题时,排查步骤如下:

  1. 设置jvm启动参数,-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./,在发生oom时自动生成dump文件。

  2. 如果是生产服务器,可以将文件发送到本地,通过jvisualvm进行排查。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!