设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间23 现在时间23 缓存数据 别怀疑自己,别改变自己。别在意别人怎么想,大胆去追求自己想要的。

别怀疑自己,别改变自己。别在意别人怎么想,大胆去追求自己想要的。

查看: 598|回复: 2

Java服务假死后续之内存溢出的原因分析

[复制链接]

  离线 

TA的专栏

  • 打卡等级:热心大叔
  • 打卡总天数:224
  • 打卡月天数:0
  • 打卡总奖励:3283
  • 最近打卡:2025-04-14 12:30:20
等级头衔

等級:晓枫资讯-上等兵

在线时间
0 小时

积分成就
威望
0
贡献
406
主题
376
精华
0
金钱
4489
积分
834
注册时间
2023-1-5
最后登录
2025-4-14

发表于 2023-2-17 12:25:33 | 显示全部楼层 |阅读模式
一、现象分析

上篇博客说到,Java服务假死的原因是使用了Guava缓存,30分钟的有效期导致Full GC无法回收内存。经过优化后,已经不再使用Guava缓存,实时查询数据。从短期效果来看,确实解决了无法回收内存的问题,但是服务运行几天后,发现内存又逐渐被占满,Full GC后只能回收一小部分。
132701tmix2zhvjxpp7pxy.png

从上图可以看出,一次Full GC后,老年代基本上没有回收多少内存,占比从99.86%降到99.70%。

二、原因排查

到底是什么对象占据这么大的内存,并且无法被JVM垃圾回收呢。在上一篇博客中已经移除了Guava缓存,按理说不应该有无法回收的对象了。那么,很明显这应该是代码问题导致了内存泄露,现在需要知道哪些对象无法被回收,从而定位出代码哪里有BUG。这里采用jmap -histo:live 201349|head -10命令打印出GC后存活的对象。
132702vfknn01wz3v0tp77.png

从上图可以看出,还是之前存在Guava缓存里面的对象占据着大部分内存,代码修改为实时查询后,每次用完数据都会从Map中剔除,按理不应该有强引用去引用这些对象。光看代码无法排查出哪里导致了内存泄露,只能将GC后的内存文件导出来进行分析。这里采用jmap -dump:format=b,file=/data/heap.hprof命令将内存文件导出来,用JDK自带的visualVM打开。
132702wegw5uwsck4kshhj.png

这里拿ECBug对象进行分析,从引用关系可以看出,ECBug对象被DataSetCenter引用,DataSetCenter就是实时查询数据进行存储的一个ConcurrentHashMap,但每次用完数据后都会进行remove操作,具体代码如下所示。
  1. private List<BusinessBean> realTimeQueryBusinessModelData(IDataSetKey accessCacheDataSetKey,Set<IMapper> mappers, Set<IFilter> filters, Set<ISorter> sorters) throws DataNotFoundException, IllegalAccessException, CloneNotSupportedException, InstantiationException {
  2.         List<BusinessBean> resultBeans = null;
  3.         try {
  4.             lock.lock();
  5.             if (!dataSetCenter.containsKey(accessCacheDataSetKey)) {
  6.                 log.info("put DataSetKey into DataSetCenter,dataSetKey is {}",accessCacheDataSetKey);
  7.                 int count = businessModelQuery.count(accessCacheDataSetKey);
  8.                 if (count == 0) throw new DataNotFoundException();
  9.                 Class modelClass = businessModelCenter.getDataModelClass(accessCacheDataSetKey.getModelId());
  10.                 if (modelClass == null) {
  11.                     throw new DataNotFoundException();
  12.                 }
  13.                 dataSetCenter.put(accessCacheDataSetKey, new DataSet(count, modelClass));
  14.             }
  15.             List<BusinessBean> cachedBeans = dataSetCenter.get(accessCacheDataSetKey).getData();
  16.             resultBeans =  getModelDataInternal(accessCacheDataSetKey, businessModelQuery, mappers, filters, sorters, cachedBeans);
  17.         }finally {
  18.             lock.unlock();
  19.             if(!lock.isLocked()){
  20.                 dataSetCenter.remove(accessCacheDataSetKey);
  21.             }
  22.         }
  23.         return resultBeans;
  24.     }
复制代码
从代码来看,每次dataSetCenter.put(accessCacheDataSetKey, new DataSet(count, modelClass))后,都会在finally里面调用dataSetCenter.remove(accessCacheDataSetKey)把key删除掉,这样在GC时会自动回收Value值。但是忽略了一个方法getModelDataInternal,该方法可能会递归调用realTimeQueryBusinessModelData方法,如果存在递归调用的话,那么由于可重入锁lock还没有完成解锁,所以无法进入if(!lock.isLocked())条件语句中进行删除key的操作,这样就造成了一部分数据无法被删除,随着时间的推移,内存中的数据会越来越多。

三、故障解决

基于上述的代码分析,改造如下所示。
  1. private List<BusinessBean> realTimeQueryBusinessModelData(IDataSetKey accessCacheDataSetKey,Set<IMapper> mappers, Set<IFilter> filters, Set<ISorter> sorters) throws DataNotFoundException, IllegalAccessException, CloneNotSupportedException, InstantiationException {
  2.         List<BusinessBean> resultBeans = null;
  3.         try {
  4.             queryLock.lock();
  5.             modelQueryLock.lock();
  6.             if (!dataSetCenter.containsKey(accessCacheDataSetKey)) {
  7.                 log.info("put DataSetKey into DataSetCenter,dataSetKey is {}",accessCacheDataSetKey);
  8.                 int count = businessModelQuery.count(accessCacheDataSetKey);
  9.                 if (count == 0) throw new DataNotFoundException();
  10.                 Class modelClass = businessModelCenter.getDataModelClass(accessCacheDataSetKey.getModelId());
  11.                 if (modelClass == null) {
  12.                     throw new DataNotFoundException();
  13.                 }
  14.                 dataSetCenter.put(accessCacheDataSetKey, new DataSet(count, modelClass));
  15.             }
  16.             List<BusinessBean> cachedBeans = dataSetCenter.get(accessCacheDataSetKey).getData();
  17.             resultBeans =  getModelDataInternal(accessCacheDataSetKey, businessModelQuery, mappers, filters, sorters, cachedBeans);
  18.         }finally {
  19.             modelQueryLock.unlock();
  20.             if(!modelQueryLock.isLocked()){
  21.                 removeDataSetKeys();
  22.             }
  23.             queryLock.unlock();
  24.         }
  25.         return resultBeans;
  26.     }
复制代码
这里当modelQueryLock可重入锁完全解锁后,调用removeDataSetKeys方法,该方法会将dataSetCenter里面的key全部删除,这样在GC时就会回收不用的数据对象。这里采用两个可重入锁的目的是,如果只用一个modelQueryLock可重入锁,那么当modelQueryLock完全解锁后,正在执行removeDataSetKeys方法时,其他线程就可以进入该方法区,发现dataSetCenter里面还没有删除完全,从而获取里面的数据,即if (!dataSetCenter.containsKey(accessCacheDataSetKey))为false,从而通过List<BusinessBean> cachedBeans = dataSetCenter.get(accessCacheDataSetKey).getData()直接获取dataSetCenter里面的数据,但是下一刻dataSetCenter里面可能已经为空。因此,采用两个可重入锁,防止出现异常。
到此这篇关于Java服务假死后续之内存溢出的文章就介绍到这了,更多相关Java 内存溢出内容请搜索晓枫资讯以前的文章或继续浏览下面的相关文章希望大家以后多多支持晓枫资讯!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

  • 打卡等级:即来则安
  • 打卡总天数:19
  • 打卡月天数:0
  • 打卡总奖励:235
  • 最近打卡:2025-04-01 19:05:15
等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
271
积分
40
注册时间
2023-2-8
最后登录
2025-4-1

发表于 2023-5-23 21:50:57 | 显示全部楼层
感谢分享~~~~学习学习~~~~~
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

  • 打卡等级:常驻代表
  • 打卡总天数:33
  • 打卡月天数:0
  • 打卡总奖励:403
  • 最近打卡:2025-06-27 23:51:57
等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
458
积分
70
注册时间
2023-1-9
最后登录
2025-6-27

发表于 前天 18:26 | 显示全部楼层
感谢楼主分享。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼
3楼

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表