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&nbsp;内存溢出内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java虚拟机内存溢出及泄漏实例

    测试参数设置: 1.循环调用new A()实现堆溢出,java.lang.OutOfMemoryError: Java heap space, 虚拟机参数:-Xms1M -Xmx1M -XX:+HeapDumpOnOutOfMemoryError,解释:将-Xmx和-Xms设置为一样可以避免堆自动扩展,-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出异常时Dump出当前的堆内存转储快照 // while (true){ // new A().do2();

  • 详解Java内存溢出的几种情况

    JVM(Java虚拟机)是一个抽象的计算模型.就如同一台真实的机器,它有自己的指令集和执行引擎,可以在运行时操控内存区域.目的是为构建在其上运行的应用程序提供一个运行环境.JVM可以解读指令代码并与底层进行交互:包括操作系统平台和执行指令并管理资源的硬件体系结构. 1. 前言 JVM提供的内存管理机制和自动垃圾回收极大的解放了用户对于内存的管理,大部分情况下不会出现内存泄漏和内存溢出问题.但是基本不会出现并不等于不会出现,所以掌握Java内存模型原理和学会分析出现的内存溢出或内存泄漏,对于使用J

  • Java 堆内存溢出原因分析

    前言 任何使用过基于 Java 的企业级后端应用的软件开发者都会遇到过这种低劣.奇怪的报错,这些报错来自于用户或是测试工程师: java.lang.OutOfMemoryError:Java heap space. 为了弄清楚问题,我们必须返回到算法复杂性的计算机科学基础,尤其是"空间"复杂性.如果我们回忆,每一个应用都有一个最坏情况特征.具体来说,在存储维度方面,超过推荐的存储将会被分配到应用程序上,这是不可预测但尖锐的问题.这导致了堆内存的过度使用,因此出现了"内存不够&

  • Java 内存溢出的原因和解决方法

    你是否遇到过Java应用程序卡顿或突然崩溃的情况?您可能遇到过Java内存泄漏.在本文中,我们将深入研究Java内存泄漏的确切原因,并推荐一些最好的工具来防止内存泄漏发生. 什么是JAVA内存泄漏? 简单地说,Java内存泄漏是指对象不再被应用程序使用,而是在工作内存中处于活动状态. 在Java和大多数其他编程语言中,垃圾收集器的任务是删除不再被应用程序引用的对象.如果不选中,这些对象将继续消耗系统内存,并最终导致崩溃.有时java内存泄漏崩溃不会输出错误,但通常错误会以java.lang.Ou

  • Java通过导出超大Excel文件解决内存溢出问题

    前言 将业务数据导出到Excel表中,导出任务数据量较大时,导出的项目就会内存溢出,本文通过Java操作Poi的SXSSFWorkbook类进行导出,解决内存溢出问题. 1.采用Poi中的SXSSFWorkbook 在实现excel导出时,在数据量过大的情况下,总是容易发生内存溢出的情况.可以使用POI提供的 SXSSFWorkbook 类来避免内存溢出. 2.maven中引入Poi <!-- poi start --> <dependency> <groupId>or

  • Java 常见的几种内存溢出异常的原因及解决

    内存溢出的异常有很多,并且每种内存溢出都会有不同的异常信息和解决方式,下面会列出常见的几种内存溢出异常 堆内存溢出 java.lang.OutOfMemoryError: Java heap space 原因: 当堆内存不足,并且已经达到JVM设置的最大值,无法继续申请新的内存,存活的对象在堆内存中无法被回收,那么就会抛出该异常,表示堆内存溢出. 当一次从数据库查询大量数据,堆内存没有足够的内存可以存放大量的数据 大量的强引用对象在堆内存中存活,GC无法回收这些对象,新创建的对象在新生代无法进行

  • 以Java代码的方式总结几个典型的内存溢出案例

    一.图示 我们先来看看今天要介绍哪些内存溢出案例,这里总结了一张图,如下所示. 二.定义主类结构 首先,我们创建一个名称为BlowUpJVM的类,之后所有的案例实验都是基于这个类进行.如下所示. public class BlowUpJVM { } 三.栈深度溢出 public static void testStackOverFlow(){ BlowUpJVM.testStackOverFlow(); } 栈不断递归,而且没有处理,所以虚拟机栈就不断深入不断深入,栈深度就这样溢出了. 四.永久

  • Java基础之堆内存溢出的解决

    一.实战-内存溢出 堆内存溢出 栈内存溢出 方法区溢出 直接内存溢出 二.实战-堆内存溢出 演示堆内存溢出代码,并且定位问题 总结堆内存溢出的场景与解决方案 分析商城项目中可能存在堆内存溢出的代码并且解决 三.堆内存溢出演示代码 public class HeapOOMTest { private List<String> oomList = new ArrayList<>(); public static void main(String[] args) { HeapOOMTes

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

    目录 一.现象分析 二.原因排查 三.故障解决 一.现象分析 上篇博客说到,Java服务假死的原因是使用了Guava缓存,30分钟的有效期导致Full GC无法回收内存.经过优化后,已经不再使用Guava缓存,实时查询数据.从短期效果来看,确实解决了无法回收内存的问题,但是服务运行几天后,发现内存又逐渐被占满,Full GC后只能回收一小部分. 从上图可以看出,一次Full GC后,老年代基本上没有回收多少内存,占比从99.86%降到99.70%. 二.原因排查 到底是什么对象占据这么大的内存,

  • Java服务假死之生产事故的排查与优化问题

    目录 一.现象 二.排查 1.打印堆栈 2.查看socket连接 3.查看JVM基本信息 4.查看GC日志 5.分析dump文件 三.优化 四.总结 一.现象 在服务器上通过curl命令调用一个Java服务的查询接口,半天没有任何响应.关于该服务的基本功能如下: 1.该服务是一个后台刷新指示器的服务,即该服务会将用户需要的指示器数据提前计算好,放入redis中,当用户请求指示器数据时便从redis中获取: 2.指示器涉及到的模型数据更新时会发送消息到kafka,该服务监听kafka消息,收到消息

  • JAVA程序内存溢出问题原因分析

    本文较为详细的分析了JAVA程序内存溢出问题原因.分享给大家供大家参考.具体如下: 遇到一个线上系统报 java.lang.OutOfMemoryError: PermGen space 错误,需要定位一下问题.很久之前到时弄过这个,现在还真有点不记得了,但这个真的是一个非常有意思的问题,值得好好研究一下.首先第一反应当然是加上-XX:+PrintGCDetails参数来看具体的GC日志,但是由于程序是tomcat启动的,担心里面封装的东西太多不好定位,既然在windows下面,所以还是借助可视

  • 解决Java导入excel大量数据出现内存溢出的问题

    问题:系统要求导入40万条excel数据,采用poi方式,服务器出现内存溢出情况. 解决方法:由于HSSFWorkbook workbook = new HSSFWorkbook(path)一次性将excel load到内存中导致内存不够. 故采用读取csv格式.由于csv的数据以x1,x2,x3形成,类似读取txt文档. private BufferedReader bReader; /** * 执行文件入口 */ public void execute() { try { if(!path.

  • Java中ThreadLocal 导致内存 OOM 的原因分析

    目录 原因分析 正确的使用方式 原因分析 ThreadLocal 导致内存 OOM 的原因是什么? ThreadLocal 底层通过 ThreadLocalMap 存储数据 源码如下:  当我们使用ThreadLocal.set()时,set的value与key(即业务自己定义的ThreadLocal类)会存储在ThreadLocalMap的Entry[]数组里 源码如下: 其中Entry是实现了一个弱引用WeakReference,Entry的key(即业务方定义的 ThreadLocal类)

  • Java内部类持有外部类导致内存泄露的原因与解决方案详解

    目录 简介 为什么要持有外部类 实例:持有外部类 实例:不持有外部类 实例:内存泄露 不会内存泄露的方案 简介 说明 本文介绍Java内部类持有外部类导致内存泄露的原因以及其解决方案. 为什么内部类持有外部类会导致内存泄露? 非静态内部类会持有外部类,如果有地方引用了这个非静态内部类,会导致外部类也被引用,垃圾回收时无法回收这个外部类(即使外部类已经没有其他地方在使用了). 解决方案 1.不要让其他的地方持有这个非静态内部类的引用,直接在这个非静态内部类执行业务. 2.将非静态内部类改为静态内部

  • 导致MyEclipse内存不足的原因分析及解决办法

    1.修改eclipse.ini 在Myeclipse安装目录下G:\MyEclipse8.5\Genuitec\MyEclipse 8.5有一个myeclipse.ini配置文件,设置如下: -vmargs -Xmx512m -XX:MaxPermSize=256m -XX:ReservedCodeCacheSize=64m 2.设置Default VM Arguments 在myEclipse中,打开Windows-> Preferences->Java->Installed JREs

  • Java内存溢出实现原因及解决方案

    1.JVM Heap(堆)溢出:java.lang.OutOfMemoryError: Java heap space JVM在启动的时候会自动设置JVM Heap的值, 可以利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置.Heap的大小是Young Generation 和Tenured Generaion 之和.在JVM中如果98%的时间是用于GC,且可用的Heap size 不足2%的时候将抛出此异常信息. 解决方法:手动设置JVM Heap(堆)的大小. Java堆用于储存

  • Java中char[]输出不是内存地址的原因详解

    前言 Java中共有八种基本数据类型:byte,int,short,long,float,double,char,boolean. 计算机中的基础数据单位是bit, 1byte=8bit. 数据类型 存储大小 举例 注释 包装类 byte 1byte 3 字节 Byte int 4byte 4 整数 Integer short 2bytes 5 短整数 Short long 8bytes 6 长整数 Long float 4bytes 1.3 单精度浮点型 Float double 8bytes

随机推荐