java程序内存溢出找原因,java 内存泄露 内存溢出
00-1010一、现象分析二。原因调查三。问题解答
00-1010上一篇博客说Java服务假死的原因是使用了番石榴缓存,30分钟的有效期让Full GC无法回收内存。优化后,Guava cache不再用于实时查询数据。从短期效果来看,确实解决了记忆无法恢复的问题。但服务运行几天后发现内存逐渐被填满,满GC后只能恢复一小部分。
从上图可以看出,经过一次完整的GC,旧时代恢复的内存基本不多,占99.86%到99.70%。
00-1010什么对象占用这么大内存,不能被JVM垃圾回收?番石榴缓存已在上一篇博客中删除。按理说,应该没有不能回收的物件。那么,很明显这应该是导致内存泄漏的代码问题。现在需要知道哪些对象不能回收,从而定位代码哪里有bug。这里,jmap-histo 3360 live 201349 head-10命令用于打印出GC后幸存的对象。
从上图可以看出,占据大部分内存的仍然是之前存在于Guava cache中的对象。代码修改为实时查询后,每次数据用完都会从地图上删除。没有强引用来引用这些对象是合理的。光看代码是找不到导致内存泄漏的原因的。您只能在GC后导出内存文件进行分析。这里,命令jmap-dump3360format=b,file=/data/heap.hprof用于导出内存文件,并使用JDK附带的visualVM打开它。
这里分析ECBug对象。从引用关系可以看出,ECBug对象被DataSetCenter引用,dataset center是一个实时查询数据进行存储的ConcurrentHashMap。然而,每次数据用完,它将被删除。具体代码如下所示。
private ListBusinessBean realTimeQueryBusinessModelData(IDataSetKey accessCacheDataSetKey、SetIMapper mappers、SetIFilter filters、SetISorter)抛出DataNotFoundException、IllegalAccessException、CloneNotSupportedException、instantiation exception { ListBusinessBean result beans=null;请尝试{ lock . lock();如果(!dataSetCenter . contains key(accessCacheDataSetKey)){ log . info(将DataSetKey放入dataset center,DataSetKey为{} ,accessCacheDataSetKey);int count=business model query . count(accessCacheDataSetKey);if (count==0)抛出新的DataNotFoundException();class model class=business model center . get data model class(accesscachedatasetkey . get modelid());if(model class==null){ throw new DataNotFoundException();} DataSet center . put(accessCacheDataSetKey,新数据集(count,model class));} ListBusinessBean cachedBeans=dataset center . get(accessCacheDataSetKey)。get data();result beans=getmodeldata internal(accessCacheDataSetKey,businessModelQuery,mappers,filters,sorters,cachedBeans);}最后{ 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 内存溢出内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。