WMQMessageConsumer构造函数抛JMSException导致的WMQFFSTInfo内存泄漏

问题描述

公司基于IBMMQ开发了企业级综合服务管理平台(ESB),同时将用户、客户、机构等等基础信息管理功能分离出来提供基本的数据服务,而其他系统则通过综合服务管理平台进行服务访问。我方恰好设计、并开发了其中的用户信息管理系统。该系统需要管理企业范围内的所有用户信息、机构信息、系统信息、功能信息、权限信息,并提供各种用户认证服务。其中一个较为关键的功能是,我们需要将各业务系统相关的用户、机构以及功能权信息实时同步到各业务系统,并且保证同步功能的高效、稳定与可靠。数据信息实时同步功能,依托企业级综合服务管理平台,并且采用异步消息通信的方式,从系统Server端将变化的信息同步到各业务系统的Client端,Server端在我方部署,Client端在各业务系统部署。我方将Client端开发完成,然后通知所有需要使用我方信息的系统下载并安装Client端。Client端一旦启动,通常不会停止。除非:(1)人为的将Client端关闭;(2)Client端进程宕掉。近期,项目组收到业务系统发来的问题求助:该业务系统在部署Client端之后,过了一段时间之后,Client端的进程宕了。在Client端的启动目录下,产生了一批javacore和heapdump文件。希望项目组能够协助分析、并解决该问题。由于整个企业范围内大约有上百个Client端安装,绝大多数Client端都是没有出现这样的问题的。由此分析,此案例只是个例,如果是个例,那么便于该业务系统的环境存在些许关系。第一步,对javacore文件进行分析由于javacore分析工具jca有些异样,我们只好使用Editplus将javacore打开,看看其中的内容究竟是怎么回事。Javacore文件一开始便告诉我们:java.lang.OutOfMemoryError“javaheapspacereceived”。即,本次导致进程宕掉的主要原因,是JVM的堆空间出现了内存溢出。在Javacore后面的线程堆栈输出方面,可以看到大量的线程都处于等待状态。既然是堆空间溢出,那么我们有必要分析下heapdump文件,看看是什么对象占用了大量的数据空间。第二步,对heapdump文件进行分析由于heapdump文件是二进制文件,并且文件大小比较庞大,不可能像javacore那样使用编辑工具软件便可以进行分析,而是需要使用专门的分析软件,例如IBMHeapAnalyzer分析工具。将heapdump文件导入到IBMHeapAnalyzer工具后,可以看到:在发生内存溢出的时候,Client端JVM进程中存在大量的java对象。通过分析工具左下方SubponenaLeakSuspects可以看到:(1)WMQFFSTInfo对象竟然占用了97%的堆内存空间。(2)WMQFFSTInfo中存在一个Map对象,包含7900个HashMap$Entry对象,每个Entry对象的大小为113K左右。(3)每个HashMap$Entry对象都涉及到WMQMessageConsumer对象。于是,我们基本上可以判定,是WMQFFSTInfo发生了内存泄漏。但是至于为什么发生,还不是特别明朗。第三步,分析WMQFFSTInfo和WMQMessageConsumer由于MQ使用的是IBM的WebSphereMQ,属于商业版本,相关的接口jar包的代码并未开源,我们只能通过反编译工具对IBMMQ的jar包进行反编译,然后分析其中的java代码实现。对于WMQFFSTInfo类,我们发现其存在4个属性,其中,每个属性都有对应的add和remove方法,WMQFFSTInfo类本身是没有什么问题的。PublicclassWMQFFSTInfo{……….Mapconnection;Mapsession;Mapconsumer;Mapproducer;………..PublicstaticvoidaddConsumer(WMQMessageConsumerconsumer){……….key=func(consumer);consumer.put(key,consumer);………}PublicstaticremoveConsumer(WMQMessageConsumerconsumer){……………key=func(consumer);consumer.remove(key);……….}……….}

下面我们来看看WMQMessageConsumer类:(1)有两个构造函数,这两个构造函数中,首先对对象属性进行this.prop=prop式的赋值,然后调用WMQFFSTInfo的addConsumer方法:WMQFFSTInfo.addConsumer(this);(2)存在close方法,在close方法中会调用WMQFFSTInfo.removeConsumer(this);单纯从上面的类和方法中,我们并不能得出任何结论。第四步,分析Client端输出的日志从Client端输出的日志中,发现在出现JVM宕掉之前,日志中大量输出JMSException,其主要内容是说:XXXXMQ队列没有找到。后来与Client端开发人员交流,得知,如果Client端在建立MQ连接后,若创建MessageConsumer失败,则会每个5秒钟,再次重复创建MessageConsumer。并且,MessageConsumer的创建次数并没有上限限制。结合前面分析的结果,在加上Client软件运行良好的系统并没有出现此问题,可以得知:Client端每隔5秒钟创建了一个MessageConsumer对象,并且将此对象放入了WMQFFSTInfo中,等到真正需要去连接MQ队列时,却由于MQ队列信息不正确而抛出了异常,导致系统根本没有时机去调用MessageConsumer的close方法释放已经放入WMQFFSTInfo的对象,在创建7900多个MessageConsumer之后,WMQFFSTInfo成功地消耗掉了JVM进程中所有的堆空间,而且还无法进行垃圾回收,最终导致JVM堆内存溢出,进而引发JVM进程宕机。第五步,问题的根本原因根据上面的分析,基本上已经知道问题原因了,但是所有的问题的根本原因还是软件的代码存在Bug,比如,本案例的Bug产生原因是,MQ队列(queue)无法正常连接,系统抛出JMSException之后,并没有将WMQFFSTInfo中的废弃WMQMessageConsumer对象移除,导致内存泄漏。按照正常情况,如果调用了WMQMessageConsumer的close方法,则系统必然会从WMQFFSTInfo中将consumer移除。我们进而需要分析Spring框架下,DefaultMessageListenerContainer的代码中,是否支持在异常发生的情况下,会调用consumer的close方法。通过反编译工具,反编译spring子项目jms的jar包,找到DefaultMessageListenerContainer的内部运行线程,可以看到线程方法中,存在一个catch(Throwablee){}finally{consumer.close();}的代码,由此判断,当consumer抛出异常时,肯定会执行close()方法。然而,事实是,Client端进程抛出JMSException时,根本就没有调用close()方法。即,似乎是在告诉我们,是在创建consumer对象的时候抛出的异常,而不是在创建成功之后,抛出的异常,所以,无法调用consumer的close方法。我们将WMQMessageConsumer的构造函数拿出来分析分析:publicWMQMessageConsumer(WMQDestinationdestination,WMQSessionsession,Stringselector,booleannolocal,JmsPropertyContextjmsProps)throwsJMSException{super(jmsProps);this.destination=destination;this.session=session;this.selector=selector;this.nolocal=nolocal;WMQFFSTInfo.addConsumer(this);checkDestinationValidForNPHigh();this.syncShadow=newWMQSyncConsumerShadow(this,session,destination,selector,nolocal,this.subscriptionName);this.currentShadow=this.syncShadow;this.currentShadow.initialize();}

WMQMessageConsumer的构造函数抛出了一个JMSException,并且是在WMQFFSTInfo.addConsumer(this);之后抛出。可见,此处为问题的根本原因所在。由此,问题不难从根本上解决之。正常情况下,在WMQMessageConsumer的构造函数中,需将抛出JMSException的Java代码用try-catch代码块封装,并且在出现异常时,在catch代码块中显示调用WMQFFSTInfo.removeConsumer(this)方法。至于为什么IBM的工程师们,不按常规的办法做,而是使用了会产生内存泄漏的解决办法,我们无法得知其中缘由。这一缺陷IBMMQ7.5版本中依然存在。【请解释】

解决方案

时间: 2024-08-02 04:51:39

WMQMessageConsumer构造函数抛JMSException导致的WMQFFSTInfo内存泄漏的相关文章

在 JNI 编程中避免内存泄漏

简介: 本文详细论述如何在 JNI 编程中避免内存泄漏.论述了 JNI 编程中可能引发的明显的内存泄漏.本文的重点是阐述 JNI 编程中潜在的内存泄漏,希望读者通过本文对 Local reference 有更深刻的理解,了解 Local reference 表的存在,区分 Local reference 和局部变量,从而认识到 Local reference 可能引发的 native memory 内存泄漏 JNI 编程简介 JNI,Java Native Interface,是 native

解析Java的JNI编程中的对象引用与内存泄漏问题_java

JNI,Java Native Interface,是 native code 的编程接口.JNI 使 Java 代码程序可以与 native code 交互--在 Java 程序中调用 native code:在 native code 中嵌入 Java 虚拟机调用 Java 的代码. JNI 编程在软件开发中运用广泛,其优势可以归结为以下几点: 利用 native code 的平台相关性,在平台相关的编程中彰显优势. 对 native code 的代码重用. native code 底层操作

Android性能优化之内存泄漏

综述 内存泄漏(memory leak)是指由于疏忽或错误造成程序未能释放已经不再使用的内存.那么在Android中,当一个对象持有Activity的引用,如果该对象不能被系统回收,那么当这个Activity不再使用时,这个Activity也不会被系统回收,那这么以来便出现了内存泄漏的情况.在应用中内出现一次两次的内存泄漏获取不会出现什么影响,但是在应用长时间使用以后,若是存在大量的Activity无法被GC回收的话,最终会导致OOM的出现.那么我们在这就来分析一下导致内存泄漏的常见因素并且如何

Java中由substring方法引发的内存泄漏

在Java中我们无须关心内存的释放,JVM提供了内存管理机制,有垃圾回收器帮助回收不需要的对象.但实际中一些不当的使用仍然会导致一系列的内存问题,常见的就是内存泄漏和内存溢出 内存溢出(out of memory ) :通俗的说就是内存不够用了,比如在一个无限循环中不断创建一个大的对象,很快就会引发内存溢出. 内存泄漏(leak of memory) :是指为一个对象分配内存之后,在对象已经不在使用时未及时的释放,导致一直占据内存单元,使实际可用内存减少,就好像内存泄漏了一样. 由substri

详解Android性能优化之内存泄漏_Android

综述 内存泄漏(memory leak)是指由于疏忽或错误造成程序未能释放已经不再使用的内存.那么在Android中,当一个对象持有Activity的引用,如果该对象不能被系统回收,那么当这个Activity不再使用时,这个Activity也不会被系统回收,那这么以来便出现了内存泄漏的情况.在应用中内出现一次两次的内存泄漏获取不会出现什么影响,但是在应用长时间使用以后,若是存在大量的Activity无法被GC回收的话,最终会导致OOM的出现.那么我们在这就来分析一下导致内存泄漏的常见因素并且如何

详解Android性能优化之内存泄漏

综述 内存泄漏(memory leak)是指由于疏忽或错误造成程序未能释放已经不再使用的内存.那么在Android中,当一个对象持有Activity的引用,如果该对象不能被系统回收,那么当这个Activity不再使用时,这个Activity也不会被系统回收,那这么以来便出现了内存泄漏的情况.在应用中内出现一次两次的内存泄漏获取不会出现什么影响,但是在应用长时间使用以后,若是存在大量的Activity无法被GC回收的话,最终会导致OOM的出现.那么我们在这就来分析一下导致内存泄漏的常见因素并且如何

缺省构造函数不能处理隐式超构造函数抛出的异常类型 IOException。必须定义显式构造函数

问题描述 缺省构造函数不能处理隐式超构造函数抛出的异常类型 IOException.必须定义显式构造函数 int lastnum = getNum(source.getProperty(""fileName"")); public static int getNum(String Filename) throws IOException { InputStream myxls; myxls = new FileInputStream(Filename); sr =

精华阅读第 13 期 |常见的八种导致 APP 内存泄漏的问题

本期是移动开发精英俱乐部的第13期文章,都是以技术为主,所以这里就不过多的进行赘述了,我们直接看干货内容吧!本文系ITOM管理平台OneAPM整理. 实际项目中的MVVM(积木)模式–序章 导读:开篇之前,先贴上以该设计模式为基础的iOSAPP的App Store地址:https://appsto.re/cn/neiscb.i 这个项目通过笔者所要讲的设计模式,三个人在同时需要忙于其他项目维护的情况下,从开工到上架,前前后后加起来用了一个月的时间.因此,在保证项目质量的前提下,敏捷开发以及如何保

未释放事件Handler可能导致内存泄漏

以前曾看见过这样一个问题:托管代码会不会导致内存泄漏.自己对GC的了解也不是很深,但还是比较赞成这样的观点:托管代码不会产生内存泄漏,除非你没有正确释放非托管资源. 今天看到一个非常有趣的例子,关于没有释放事件的Handler导致的内存泄漏. 以前对于释放Handler的观念是一点也没有,这主要因为没此方面的意识,没有养成好的习惯.只知道当关心这个事件的时候就注册一下, 暂时不关心了就移除掉.却从来没有想到最终不移除不必要的Handler会导致此类无法被正常回收,导致不必要的内存浪费. 事情是这