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

从上图可以看出,一次Full GC后,老年代基本上没有回收多少内存,占比从99.86%降到99.70%。
二、原因排查
到底是什么对象占据这么大的内存,并且无法被JVM垃圾回收呢。在上一篇博客中已经移除了Guava缓存,按理说不应该有无法回收的对象了。那么,很明显这应该是代码问题导致了内存泄露,现在需要知道哪些对象无法被回收,从而定位出代码哪里有BUG。这里采用jmap -histo:live 201349|head -10命令打印出GC后存活的对象。

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

这里拿ECBug对象进行分析,从引用关系可以看出,ECBug对象被DataSetCenter引用,DataSetCenter就是实时查询数据进行存储的一个ConcurrentHashMap,但每次用完数据后都会进行remove操作,具体代码如下所示。
private List<BusinessBean> realTimeQueryBusinessModelData(IDataSetKey accessCacheDataSetKey,Set<IMapper> mappers, Set<IFilter> filters, Set<ISorter> sorters) throws DataNotFoundException, IllegalAccessException, CloneNotSupportedException, InstantiationException {
List<BusinessBean> resultBeans = null;
try {
lock.lock();
if (!dataSetCenter.containsKey(accessCacheDataSetKey)) {
log.info("put DataSetKey into DataSetCenter,dataSetKey is {}",accessCacheDataSetKey);
int count = businessModelQuery.count(accessCacheDataSetKey);
if (count == 0) throw new DataNotFoundException();
Class modelClass = businessModelCenter.getDataModelClass(accessCacheDataSetKey.getModelId());
if (modelClass == null) {
throw new DataNotFoundException();
}
dataSetCenter.put(accessCacheDataSetKey, new DataSet(count, modelClass));
}
List<BusinessBean> cachedBeans = dataSetCenter.get(accessCacheDataSetKey).getData();
resultBeans = getModelDataInternal(accessCacheDataSetKey, businessModelQuery, mappers, filters, sorters, cachedBeans);
}finally {
lock.unlock();
if(!lock.isLocked()){
dataSetCenter.remove(accessCacheDataSetKey);
}
}
return resultBeans;
}
从代码来看,每次 dataSetCenter.put(accessCacheDataSetKey, new DataSet(count, modelClass))后,都会在finally里面调用dataSetCenter.remove(accessCacheDataSetKey)把key删除掉,这样在GC时会自动回收Value值。但是忽略了一个方法getModelDataInternal,该方法可能会递归调用realTimeQueryBusinessModelData方法,如果存在递归调用的话,那么由于可重入锁lock还没有完成解锁,所以无法进入if(!lock.isLocked())条件语句中进行删除key的操作,这样就造成了一部分数据无法被删除,随着时间的推移,内存中的数据会越来越多。
三、故障解决
基于上述的代码分析,改造如下所示。
private List<BusinessBean> realTimeQueryBusinessModelData(IDataSetKey accessCacheDataSetKey,Set<IMapper> mappers, Set<IFilter> filters, Set<ISorter> sorters) throws DataNotFoundException, IllegalAccessException, CloneNotSupportedException, InstantiationException {
List<BusinessBean> resultBeans = null;
try {
queryLock.lock();
modelQueryLock.lock();
if (!dataSetCenter.containsKey(accessCacheDataSetKey)) {
log.info("put DataSetKey into DataSetCenter,dataSetKey is {}",accessCacheDataSetKey);
int count = businessModelQuery.count(accessCacheDataSetKey);
if (count == 0) throw new DataNotFoundException();
Class modelClass = businessModelCenter.getDataModelClass(accessCacheDataSetKey.getModelId());
if (modelClass == null) {
throw new DataNotFoundException();
}
dataSetCenter.put(accessCacheDataSetKey, new DataSet(count, modelClass));
}
List<BusinessBean> cachedBeans = dataSetCenter.get(accessCacheDataSetKey).getData();
resultBeans = getModelDataInternal(accessCacheDataSetKey, businessModelQuery, mappers, filters, sorters, cachedBeans);
}finally {
modelQueryLock.unlock();
if(!modelQueryLock.isLocked()){
removeDataSetKeys();
}
queryLock.unlock();
}
return resultBeans;
}
这里当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服务假死
一.现象 在服务器上通过curl命令调用一个Java服务的查询接口,半天没有任何响应.关于该服务的基本功能如下: 1.该服务是一个后台刷新指示器的服务,即该服务会将用户需要的指示器数据提前计算好,放入 ...
- Tomcat9.0.13 Bug引发的java.io.IOException:(打开的文件过多 Too many open files)导致服务假死
问题背景: 笔者所在的项目组最近把生产环境Tomcat迁移到Linux,算是顺利运行了一段时间,最近一个低概率密度的(too many open files)问题导致服务假死并停止响应客户端客户端请求 ...
- 分析java进程假死状况
摘自: http://www.myexception.cn/internet/2044496.html 分析java进程假死情况 1 引言 1.1 编写目的 为了方便大家以后发现进程假死的时候能够正常 ...
- Java虚拟机系列(三)---内存溢出情况及解决方法
因为Java虚拟机内存有堆内存.方法区.虚拟机栈.本地方法栈和程序计数器五部分组成,其中程序计数器是唯一一块不会发生内存溢出异常的内存区,所以只有四类内存区可能发生内存溢出异常,其中虚拟机栈和本地方法 ...
- 【Java】几种典型的内存溢出案例,都在这儿了!
写在前面 作为程序员,多多少少都会遇到一些内存溢出的场景,如果你还没遇到,说明你工作的年限可能比较短,或者你根本就是个假程序员!哈哈,开个玩笑.今天,我们就以Java代码的方式来列举几个典型的内存溢出 ...
- 记一次阿里云oss文件上传服务假死
引言 记得以前刚开始学习web项目的时候,经常涉及到需要上传图片啥的,那时候都是把图片上传到当前项目文件夹下面,每次项目一重启图片就丢了.虽然可以通过修改/tomcat/conf/server.xml ...
- java 导出 excel 最佳实践,java 大文件 excel 避免OOM(内存溢出) excel 工具框架
产品需求 产品经理需要导出一个页面的所有的信息到 EXCEL 文件. 需求分析 对于 excel 导出,是一个很常见的需求. 最常见的解决方案就是使用 poi 直接同步导出一个 excel 文件. 客 ...
- windows环境下解决web服务假死的问题
最近在windows系统在部署web服务器,发现很不稳定.web服务有容易假死,改过配置换过各种web软件,如apache.nginx都不管用. 所以干脆做个简易的定时检测Web服务状态的软件.一旦w ...
- 解决因为终端打印造成的java程序假死
问题状态: java 程序 日志采用 log4j 运行时由另一个管理进程拉起,程序在后台运行. 现象: 程序后台运行时,运行一段时间后假死 分析原因: 尝试打印输出,定位假死的具体位置,发现出现假死的 ...
随机推荐
- Vue 生产环境(production) 打开 调试工具的方法
总所周知,在production生产环境下,点击vue插件会显示, 并且按F12是没办法使用调试工具的,这样非常不方便 其实是有一个办法的,那就是复制下面的js,然后在控制台执行 代码中已经写清楚执行 ...
- fpm工具来制作rpm包软件
第1章 rpm包的制作 1.1 fpm的概念介绍 FPM功能简单说就是将一种类型的包转换成另一种类型 1.1.1.支持的源类型 类型 说明 dir 将目录打包成所需要的类型,可以用于源码编译安装的 ...
- marginStart marginEnd就关于上述两个用法展开讨论
在我还在手动调整两个控件距离时,第一次见识到了marginStart marginEnd两个用法, 但是,只知道它俩能够调整控件距离,但是不知道具体用法,老师也没细说,就这样开启了我对于这两个用法&q ...
- Go学习-基本语法(一)
前言 一直对Service Mesh相关内容比较感兴趣,后面一路学习了Dcoker.Kubernetes等相关内容,可以说是对基本概念和使用有一定了解,随着开始学习一些相关的组件的时候,发现基本上全部 ...
- plicp 点云迭代最近邻点配准法
输入参数 点云A的极坐标集合 点云A对应Lidar所在pose 点云B的极坐标集合 点云B对应Lidar所在pose Features 根据两个点云的弧度关系确定找点的起始位置 根据两个点云的弧度关系 ...
- 在Ubuntu安装eclipse环境
下载准备 1安装jdk,笔者安装的是jdk-8u121-linux-x64 2安装eclipse,下载地址:http://www.eclipse.org/downloads/packages/ecli ...
- 3D打印CLI文件格式的读取
CLI 英文全称:COMMON LAYER INTERFACE 中文名称:通用层接口. 推荐从官方网址看一下其完整的内容: (https://www.hmilch.net/downloads/cli_ ...
- (Bezier)贝塞尔曲在路径规划的运用
前言 之前被安排了活,一个局部区域机器运动控制的工作,大致是一个机器位于一个极限区域时候,机器要进入一个特殊的机制,使得机器可以安全的走出来.其中用到了bezier曲线进行优化路径,今天写一下,正好也 ...
- 现有教学数据库JX_DB,作业
现有教学数据库JX_DB,数据库有以下三个基本表: 学生表student,它由学号sno.姓名sname.性别sex.出生日期Bdate.所在系dept五个属性构成.其中,学号不能为空,值是唯一的: ...
- 记录在EF Core级联更新时出现的错误The database operation was expected to affect 1 row(s), but actually affected 0 row(s) (低级错误导致)
错误提示:The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data ma ...