记录一次内存溢出
一.状况
线上出现了OutOfMemoryError
二.排查
- 最简单查看java内存的方法就是分析dump文件.
- 查找当前进程的Pid , pid 是 50480
- 到jdk安装目录bin下面找一个 jmap的命令
- 然后 ./jmap -dump:format=b,file=/opt/heap/heap1.bin 50480 , 得到 第一个 heap1.bin
- 过个把小时, 再使用这个命令 ./jmap -dump:format=b,file=/opt/heap/heap2.bin 50480 , 得到第二个heap2.bin
- 然后就是分析环节了, 我使用的是Eclipse的MAT插件, 具体安装过程百度之
- 用Eclipse 分别打开heap文件, 此时请看配图
当我们把两个, 都点击 Histogram 的时候, 会出现如下界面:
比较两个堆的生成情况,如下图
就是这些导致内存一路飙升, 此时的你应该能想到哪些地方使用到了FileInputStream了. 对, 没错, 就去那个地方找. 如果你对代码不熟悉的话, 也可以参照下面方法来定位位置:
- 1.打开heap2文件
- 2.生成表单
- 3.在对象表单中寻找到我们刚刚看到的FiliInputStream, 然后右击选择List Objects, 然后选择 outGoing…
- 4.然后你就可以看到这个FileInputStream到底是什么了, 这时候去代码那边找原因
我这里可以找到是如下图的这个类的这个方法:
三.原因分析
表面上看, 这段代码没什么大问题, 因为我的reader是需要在其他地方使用, 所以会在使用完之后关闭这个BufferedReader流. 然后嵌套的流也会相应的关闭. 没毛病啊.
的确BufferedReader对象在显示关闭的时候,会把嵌套的InputStreamReader流给关闭,
但是我们注意到上图红圈的地方,匿名new了一个FileInputStream对象,这个流是不会被关闭的,
同时因为 FileInputStream重写了finalize方法
**如果没有显式关闭流, jvm会有一个finalize()的方法来做最后的防线, 也就是说我们BufferedReader流虽然关闭了, 但是嵌套的流不关闭的话, 只能通过finalize()方法来关闭. **
但是在高并发情况下, 也许FileInputStream流开的很多, 但是finalize() 是单线程操作的,并且线程的优先级很低.finalize()方法会把需要释放的资源放到一个Queue中, 如果释放的动作慢于产生的速度, 这时就会有大量的Finalizer对象堆积, 没有及时释放资源,导致内存的异常.
四.解决方案
不使用匿名对象,将流依次显示关闭,问题解决