问题解析
生产运行的应用,在某些场景会发生OOM,然而OOM只是系统反馈给开发人员的一种结果现象,真正引发OOM的原因则需要开发人员去定位、分析、解决。
我们的应用可以理解为一个存在多个格子的装水的容器,垃圾回收器则是维护容器的人员;当有请求时,就相当于向容器中注水,当容器将要装满时,维护人员就会将污水放出,为新的水注入腾出空间,如果水漫过容器,那就是OOM的现象了。
OOM原因
容器的格子太小(开发人员JVM参数设定不合理)
水注入的过快(请求流量过大)
容器中水注入的量过大(单次请求产生的垃圾过多)
不能及时的清理污水(请求伴随着缓存对象、长生命周期对象、或者甚至是内存泄漏)
问题感知
当系统发生OOM问题,我们通常会通过应用健康度监控得到通知。
问题排查
通过监控平台,查看应用内存占用,观察请求信息,通过日志平台查看异常信息。
问题分析
解决OOM的中心思想在于,通过技术手段进行对象定位,然后判断当前大量存在的对象是否合理,如果不合理则需要在应用代码层面优化,如果合理则调整相关的硬件配置升级扩容、扩大JVM堆大小参数。
问题位置
堆OOM
异常类型
java.lang.OutOfMemoryError:Java heap space
设置JVM参数
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=\dir\heapdump.hprof
定位
下载dump文件,使用eclipse mat进行大对象分析定位,mat下载地址https://www.eclipse.org/mat/previousReleases.php,可根据jdk版本下载对应的mat版本,将dump导入后即可进行定位分析问题。
GC后仍无空间
java.lang.OutOfMemoryError:GC Overhead limit exceeded
原因
这个异常通常是在GC频繁发生,并且已经无法再回收到更多内存(如果连续5次GC,Java 进程花费大约 98% 以上的时间进行垃圾回收,并且如果它回收的堆空间少于 2%)
排查过程同上,可以借助MAT分析dump文件,主要看OOM前堆中占用空间最大的对象是什么,然后进一步分析是合理的占用还是内存泄漏。
元空间内存溢出
java.lang.OutOfMemoryError:Metaspace
原因
元空间存放的是类型信息等元数据,基本不会OOM(直接使用本地内存),如果发生了OOM则首先检查 -XX:MaxMetaspaceSize 参数是否设置过小(比如小于128m)。
其次排查是否程序有创建大量动态类的操作
创建的数组过大
java.lang.OutOfMemoryError:Requested array size exceeds VM limit
原因
请求过大的数组导致OOM,这个在项目中基本不太可能,编码阶段合理设置数组长度即可。
服务器交换空间不足
java.lang.OutOfMemoryError:Out of swap space
原因
这个通常是OS的问题(例如OS配置的swap空间不足、其他进程持续占用内存等),由于我们目前一个容器内基本不会运行其他进程,如果发生该问题,排查应用是否同时部署多个应用,导致服务器内存不足,可以运维同学共同排查。
直接内存OOM
通常堆栈看起来是这样的:
Exception inthread "main" java.lang.OutOfMemoryError
atsun.misc.Unsafe.allocateMemory(Native Method)
atcom.jd.fly.test.agent.Test01.main(Test01.java:15)
.......
直接内存的OOM目前已知的有NIO相关的框架直接操作DirectByteBuffer导致的,排查过程通常比较麻烦且耗时,从现象来看,一个明显的特征是在Heap Dump文件中不会看见有什么明显的异常情况,如果排查发现内存溢出之后产生的Dump文件很小,而程序中又直接或间接使用了 DirectM emory(典型的间接使用就是NIO),则可以考虑这个方向