java.lang.OutOfMemoryError Java 爆堆 bug 的排查思路

2021/12/07 solution Java 共 1479 字,约 5 分钟

前言

一般来说,爆堆爆栈这种问题出现了,都是你自己的原因,在跑程序之前没有一点:“坏了,这下可能要爆堆,先跑试试看”的预感,这爆堆就是你自己的问题。

接下来,又要分两种不同的情况。bug 可能原理很简单,易于定位,易于解决,可能是什么 concurrentHashMap 忘记释放,静态的数据忘记释放之类。毕竟你正常使用,数据的生命周期是订死的,短暂而易于判断的,他不会超出调用的栈的周期,随着调用的返回被 jvm 清理,不会出什么无法预料的问题。

重点来了,我做的是一个图像处理的东西,遇到的这个bug非常“奇怪”,它无法复现,看起来是随机发生,本地大内存跑不出问题,在测试环境架起来跑一会,在跑的途中出问题,看起来就是堆空间不够。尤其这还是一个图像有关的服务,测试服务器就1.8g 的内存,更有可能就是内存本身就是不够。

当然,还是那句话,这还应该是你的问题,只不过你需要一些帮助来看看到底是什么地方出问题了。

排查步骤

1. 简单尝试

排除一些简单的,能一眼看出来的情况:

  1. 忘记回收的 concurrentHashMap 之类的数据结构
  2. 忘记管理的不断变大的静态变量
  3. 忘记 flush,close 的流
  4. 调用其他人的包,可以查查文档,有没有手动释放,析构作用的函数,比如bufferedImage.getGraphics().dispose()
  5. 等等

2. 本地查看运行状况

jetBrains 的 Intellij 自带Profiler工具,可以说是十分好用了,不仅能看实时的内存占用曲线图,能照内存快照,还能打开hprof文件查看占用空间最多的类。

如果你发现内存确实有大规模的起伏,比对一下峰值和测试服务器的内存,那么有可能确实是其他环境的内存不够导致的堆溢出 bug。

这里要提一句了,请保证本地运行环境的测试用例和测试服务器完全一致完全一致!你并不知道问题会不会出在那些不同的测试用例上,而这种疏忽会导致长时间的痛苦。

3. 增大运行内存

通过直接增大内存,如果你的服务器有足够的内存可以使用,分配。

在启动服务,java -jar的时候增加有关堆的参数 -Xms -Xmx,初始堆和最大堆。如:java -jar -Xms512m -Xmx1024m app.jar

如果增加了参数就不报错了,那么你可以选择尝试优化一下项目,或者就把它当做解决方案。

4. 打印堆信息

让java在堆内存不够的时候打印当时堆中的状态,可以找到到底是什么东西占用了太多的空间,帮助定位问题。

在启动服务,java -jar的时候增加打印堆的参数 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./,打印堆信息和打印文件的路径。如:java -jar -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./ app.jar

在爆堆之后,会得到一个和进程号有关的 .hprof文件在你指定的路径下,可以通过intellij的profiler打开,或者jdk目录下的jvisualvm.exe程序,都可以分析,还能知道错误发生的线程之类的信息,最重要的还是找到占用最大内存的类。

后记

最后我这个爆堆的 bug 还是 de 出来了,不是内存不够的问题,我也没办法解决,这是个第三方包在 jdk1.8下的特殊 bug。似乎是升级 jdk 版本就能解决。然而升是不可能升的,1.8用到死。

还有一点,当 bug 太莫名其妙时,不要死磕,不如去干点别的事,发散发散思维,磕不出来的。

reference

jdk使用自带工具查看.hprof文件

【jvm】记一次线上Java heap space内存溢出问题排查记录

文档信息

Search

    Table of Contents