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中进行了大量的数据查询,一般来说问题不大。XNIO-2这个线程名称,看着就是对外提供的某个接口,猜测是因为某些查询条件(提供给别的组件调用的接口)偶发的大量数据查询导致。
此时将gc日志加入分析,发现定时在每天一点时,就会出现非常频繁的full gc,过了这个点之后内存明显下降,且不再触发full gc。此时去查调用方的代码发现,每天一点有一个定时同步的逻辑,基本可以确认是同步逻辑导致的,考虑进行代码的优化。

避免不需要的查询

首先是在代码中出现了"通过findByKeyIn查询表A数据,再通过查询到的ID去删除表B的数据(一个key可能对应几十万ID,更何况是keylist,这个数据量肯定是非常大的)",解决方式是既然key和表B的ID有对应关系,写入时就将key写入表B,删除时直接通过key去删除表B的数据,这样就无需对表A做查询操作。对可能有大数据量的表的查询需要谨慎处理。

注意delete语句

打开spring.jpa.show-sql后,观察sql发现使用JPA自带的deleteByXxxIn语句时,会先查询符合条件的记录(进入一级缓存)再一条一条删除。因此在大批量做删除操作时(前文提到,通过KeyList删除,而一个Key可能对应非常多个ID),不要使用JPA自带的deleteByXxxIn,而是使用@Query.

    @Modifying
    @Query("delete from table_name s where s.key in :keyList ")
    int deleteByKeyInBatch(@Param("keyList") List<String> keyList);

entityManager清空缓存

EntityManager直接@Autowired就可以被注入。如果确实查询数据量超过了内存限制,就只能在查询方法中分段查询,查完一部分就clear一下,再查下一部分。

    @Autowired
    private EntityManager entityManager;

    @Transactional
    public void clearSession() throws BaseException {
        entityManager.unwrap(Session.class).clear();
    }

后记

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

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值