Java堆内存溢出问题分析和解决

起因

组件跑着跑着自己挂掉,查看日志报java.lang.OutOfMemoryError: Java heap space,看起来是内存溢出了,具体原因不明,因此准备获取dump文件拿来分析下。

获取dump文件

JVM环境变量设置: -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${_HOME_DIR}/logs/xxx.hprof 加入该参数后,内存溢出后就会在规定目录自动生成.hprof文件了,如果是测试环境,为了快速复现,可以把内存设置改小一点,比如-Xmx512m -Xms512m -xmn128m.

另: 使用jmap -dump:format=b,file=xxx.hprof [pid] 也是可以导出dump文件的,但是这次我并没有用到,就不赘述了。

下载MAT工具

MAT是用来分析dump文件的, 注意选择自己电脑对应的版本就可以了

分析dump文件

解压MAT之后点击MemoryAnalyzer.exe,即可启动 左上角File-open heap dump,打开之后弹窗选择Leak suspects report(默认) 它就会有这样的一个内存占用情况: 显然,确实是有一个东西,把内存都占完了,但是是什么呢…看着好像是hibernate什么什么QueryParameterBindingsImpl.expandListValueParameters,但具体好像看不出什么来,此时可以打开dominator_tree 找到占用最多的部分展开,其实可以看到是StatefulPersistenceContext。随便找一个String,List objects with incoming references,也可以看到指向的还是PersistenceContext。

猜想和解决方式

PersistenceContext是hibernate的一级缓存,且hibernate的一级缓存是无法被关闭的。并且在查询时,hibernate发现如果缓存中没有,就会把数据在缓存中存一份,所以确实是很占缓存的…但是一般来说hibernate的一级缓存是会随着session的结束而回收的,所以除非在某个session中进行了大量的数据查询,一般来说问题不大。而且像这种情况一般都是有动作触发之后程序会立马崩溃。 但是其实对于我这次而言,组件好像是跑着跑着跑一段时间才挂的,所以猜想可能是因为某些查询条件(提供给别的组件调用的接口)偶发的大量数据查询导致的,也可能是因为某个session一直没有关闭导致的(不知道是否会有这种情况,才疏学浅了)。

暂时的处理方式是定时调用entityManager的clear方法清空缓存。如下:

@Autowired
    private EntityManager entityManager;

    @Scheduled(cron = "0 0 1 * * ?")
    public void clearSession() throws BaseException {
          
   
        entityManager.clear();
    }

EntityManager直接@Autowired就可以被注入。准备让组件再跑一段时间看下,是否可以不再崩溃。如果还是无法生效,就只能在查询方法中分段查询,查完一部分就clear一下,再查下一部分,或者每查完一次就clear一下。

后记

这次的问题出现在数据库有八十万数据的情况下,其实在大量数据的时候,使用hibernate确实是要谨慎。之前hibernate的queryPlanCache也引起过内存溢出问题,这次又是缓存问题。另外就是设计的时候需要考虑到数据库会不会有冗余数据,如果有的话要想办法及时删掉。

写得比较匆忙,找资料的时候看到以下两篇,粗略看下应该是可供参考的,放在这里,等后面有时间再整理这篇博客的话可能会仔细看下。

经验分享 程序员 微信小程序 职场和发展