什么是OOM?
就是我们常见的: java.lang.OutOfMemoryError ,在应用开发中,是比较常见的一种异常,主要分为三种:
OutOfMemoryError: PermGen space
OutOfMemoryError: Java heap space
OutOfMemoryError: unable to create new native thread
内存泄露 和 内存溢出 的区别
内存泄露[Memory Leak]
程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
内存溢出[Out Of Memory]
程序申请内存时,没有足够的内存供申请者使用,就是内存不够用,此时就会报错OOM,即所谓的内存溢出。
两者关系
内存泄漏的堆积最终会导致内存溢出
内存泄漏的分类
常发性内存泄漏
发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
偶发性内存泄漏
发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
一次性内存泄漏
发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。
隐式内存泄漏
程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,称这类内存泄漏为隐式内存泄漏。
OOM的危害
应用服务异常
线程异常
程序崩溃
其他未知问题
怎么解决
既然OOM这么恐怖,那么我们应该如何排查定位,并结局问题呢?
定位进程
模拟内存溢出
编写一段OutOfMemoryError测试代码,用来模拟出 OOM 场景。
1 | /** |
给程序分配内存
1 | JAVA_OPTS -Xms200m -Xmx200m |
程序启动后,并发调用一段时间后出现OOM现象。
dump堆栈信息
获取进程的PID并使用jvm自带的jmap命令dump进程的堆栈信息,如下:
1 | ps -ef | grep xiaolong-test-service |
这里可以在程序中配置启动参数,在内存溢出时,自动输出dump文件。
1 | VM options: |
使用MAT分析堆栈信息
启动MAT打开dump的文件heap.hprof进行分析。
Unreachable指的是可以被垃圾回收器回收的对象,但是由于没有GC发生,所以没有释放,这时抓的内存使用中的Unreachable就是这些对象。
Shallow Heap(浅堆) 表示该对象自身占用的堆内存,不包括它引用的对象。
针对非数组类型的对象,它的大小就是对象与它所有的成员变量大小的总和。
Retained Heap(深堆) 表示当前对象大小+当前对象可直接或间接引用到的对象的大小总和。
换句话说,Retained Size就是当前对象被GC后,从Heap上总共能释放掉的内存。
不过,释放的时候还要排除被GC Roots直接或间接引用的对象。他们暂时不会被被当做Garbage。
还能看到一些堆栈分析概述,系统信息,以及类直方图。
根据上图可知,OOMObject对象可能是造成内存泄漏的原因。