背景
在后端服务改为在 Docker + Kubernetes 上部署后没多久,程序就由于 java.lang.OutOfMemoryError: Java heap space 原因退出重新启动。
问题分析
下面是日志文件的最后输出:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /app/tmp/java_pid16.hprof ...
Heap dump file created [384896308 bytes in 5.099 secs]
Exception in thread "http-nio-8080-Acceptor-0"
导致 OOM 的直接原因就是堆内存不够了,要解决这个问题可以从两个角度出发,一个是扩大 MaxHeapSize 的值,另一个是找出程序中存在的问题,比如没有及时释放不用的对象,或者使用了很多的大对象等。下面就分别从宏观角度和微观角度去分析解决方案。
宏观角度分析
解决这个问题最简单粗暴的方法就是给 MaxHeapSize 设置更大的值。首先我们先看下程序堆内存相关信息:
jmap -heap PID
在输出的信息中看到最大堆内存为 256M (MaxHeapSize = 268435456 (256.0MB))。可以明确的是程序 OOM 的最大原因是因为堆的最大内存太小了,再去深究原因为什么 MaxHeapSize 值是 256M呢?在 JVM 的启动参数中并没有找到堆最大内存的设置,那么 JVM 在启动时就会根据可用内存计算出这个值,为可用内存的 1/4。从运维那里得知 Kubernetes 中的 单个 Pod 配置了最大可用内存为 1G,这就刚好对应上了。
经过上面的分析,需要注意的是设置最大堆内存大小,因此设置 JVM 参数: -Xmx1024m
微观角度分析
扩大堆内存最大值可以解决 OOM 问题,但是我们并没有找到真正导致 OOM 问题的原因(当然在这个例子中的最大原因是 MaxHeapSize 值太小),这时就需要分析堆内存中的具体有哪些对象了。
我们可以通过 Eclipse 的 Memory Analyzer Tool (MAT) 工具来分析堆内存 dump文件,来查看具体哪些对象比较占用内存。这里需要注意的是要设置相关的参数,用于在程序 OOM 时自动保存堆Dump文件 :
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$APP_HOME/tmp
在程序运行中也可以通过下面命令生产堆dump 文件:
jmap -dump:format=b,file=rds.bin PID
通过 MAT 工具,我们可以找到那些对象比较占用内存,在对应找到程序中相关的代码,做出相应的修改。
总结
经过上述分析,一方面要注意的是根据实际情况设置堆内存最大值,如果不设置将会根据可用内存动态计算。另一个需要注意的是要设置 OOM 时自动保存堆dump 信息,这样在出现事故时,才能够内入分析是什么原因导致的 OOM。