前言
一般来说,爆堆爆栈这种问题出现了,都是你自己的原因,在跑程序之前没有一点:“坏了,这下可能要爆堆,先跑试试看”的预感,这爆堆就是你自己的问题。
接下来,又要分两种不同的情况。bug 可能原理很简单,易于定位,易于解决,可能是什么 concurrentHashMap 忘记释放,静态的数据忘记释放之类。毕竟你正常使用,数据的生命周期是订死的,短暂而易于判断的,他不会超出调用的栈的周期,随着调用的返回被 jvm 清理,不会出什么无法预料的问题。
重点来了,我做的是一个图像处理的东西,遇到的这个bug非常“奇怪”,它无法复现,看起来是随机发生,本地大内存跑不出问题,在测试环境架起来跑一会,在跑的途中出问题,看起来就是堆空间不够。尤其这还是一个图像有关的服务,测试服务器就1.8g 的内存,更有可能就是内存本身就是不够。
当然,还是那句话,这还应该是你的问题,只不过你需要一些帮助来看看到底是什么地方出问题了。
排查步骤
1. 简单尝试
排除一些简单的,能一眼看出来的情况:
- 忘记回收的 concurrentHashMap 之类的数据结构
- 忘记管理的不断变大的静态变量
- 忘记 flush,close 的流
- 调用其他人的包,可以查查文档,有没有手动释放,析构作用的函数,比如
bufferedImage.getGraphics().dispose()
- 等等
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
【jvm】记一次线上Java heap space内存溢出问题排查记录
文档信息
- 本文作者:nyaaar
- 本文链接:https://nyaaarlathotep.github.io/2021/12/07/java%E7%88%86%E5%A0%86bug%E7%9A%84%E6%8E%92%E6%9F%A5%E6%80%9D%E8%B7%AF/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)