论坛首页 Java企业应用论坛

Struts+Spring+Hibernate内存泄漏查找与处理

浏览 60004 次
该帖已经被评为精华帖
作者 正文
   发表时间:2006-12-27  

10月份自己总结的东西,在CSDN的博客里面,后来搬家到JavaEye,但是只有通过Google才会有人看到,所以再发出来,希望能有更多人看到,欢迎交流!可能标题有点大,大家理解一下

 

上个项目交付使用有两个月了,结果前几天客户告诉我他们的服务器崩溃了,先前服务器跑了一年都没有问题,结果装上我的东西跑了两个月就崩了,当时就说的我一身汗……心想人家两个月才崩溃的,我该怎么找这问题所在啊?

       把代码拿回来找原因,用JProbe监控服务器运行状态,结果搞了好久无法找到问题。后来朋友推荐我使用JProfiler监视线程,经过几天的痛苦折磨,总算是有一点发现,但目前还不能确定是否可以解决问题。

       具体情况:

       服务器一直是空载状态(客户基本不大用的,就是服务自己在跑,我觉得C/S架构更适合他们 :( ),服务器在跑的时候内存图就是一个锯齿型的样子:

      因为服务器中出了跑着Struts+Spring+Hibernate的Web服务之外还在后台开了一个运行Socket端口的线程,还有一个Quartz的线程,他们都会每隔一会就使内存用量升高一些,但是每过一段时间JVM自动运行GC就会使得内存又回复平稳状态。然后反复比较JProfiler提供的几个统计功能:Memory views、Heap walker、CPU views,

经过多次比较,再对各种配置参数修改(停止某些服务重新监视),发现CPU使用率基本没有什么变化,证明没有多余的线程在跑,在Heap walker和Memory views中发现最可能出现问题的就是用于开辟端口的StartThread线程和一个DiskStore线程,由于StartThread只是开辟一个端口,在没有外部使用这个端口的情况下它是不会增加什么东西的,所以就从DiskStore下手,Google一下,这是一个EHCache的一个类,应该是Hibernate用到了,因为我的程序里面用到了OpenSessionInViewFilter这个烂东东,实践证明这个东西很容易出错,速度慢,占用资源也多,真是不知道怎么还那么多人用,难道我配置的有问题?即便我配置的不得当(我的基本都是文档中的标配),那至少这个破烂反模式也是破坏了层的关系,与其说是个模式,不如说是Hibernate为自己的设计失误所做的一个不大情愿的补偿,所以以后再也不想用它,也不建议大家用,除非你非常熟悉这套东西,而且服务器的负载、流量低。

       扯远了,在发现这个DiskStore之后我就迷糊了,我根本就没有在Hibernate中配置二级缓存,为什么会有这个东西自己跑出来呢?这个问题我一直没有想清楚,也只能怪自己对这几个框架还是不够清楚,以后有机会要去补习一下,最好能抽时间研究一下源代码,说不定能有不小的收获(个人认为不是谁看源代码都会有大彻大悟的感觉,毕竟那可都是些大师们的手笔,我看过一点源代码,理解起来也很慢,尤其是关系复杂起来的时候就看不下去)。

      找了好半天,也没有和DiskStore太相关的东西,没办法就继续跑服务,结果两天两夜的双休跑下来,一个服务器直接重启,一个服务假死……郁闷了, 又去网上搜索了一下,居然有意外收获,发现了一个Spring的监听器IntrospectorCleanupListener,在一个博客上找到的,是对Spring文档的一段翻译,内容如下:

spring中的提供了一个名为org.springframework.web.util.IntrospectorCleanupListener的监听器。它主要负责处理由 
JavaBeans  Introspector的使用而引起的缓冲泄露。spring中对它的描述如下:
 
它是一个在web应用关闭的时候,清除JavaBeans Introspector的监听器.在web.xml中注册这个listener.可以保证在web 应用关闭的时候释放与掉这个web 应用相关的class loader 和由它管理的类
 
如果你使用了JavaBeans Introspector来分析应用中的类,Introspector 缓冲中会保留这些类的引用.结果在你的应用关闭的时候,这些类以及web 应用相关的class loader没有被垃圾回收.
 
不幸的是,清除Introspector的唯一方式是刷新整个缓冲.这是因为我们没法判断哪些是属于你的应用的引用.所以删除被缓冲的introspection会导致把这台电脑上的所有应用的introspection都删掉.
 
需要注意的是,spring 托管的bean不需要使用这个监听器.因为spring它自己的introspection所使用的缓冲在分析完一个类之后会被马上从javaBeans Introspector缓冲中清除掉.
 
应用程序中的类从来不直接使用JavaBeans Introspector.所以他们一般不会导致内部查看资源泄露.但是一些类库和框架往往会产生这个问题.例如:Struts 和Quartz.
 
单个的内部查看泄漏会导致整个的web应用的类加载器不能进行垃圾回收.在web应用关闭之后,你会看到此应用的所有静态类资源(例如单例).这个错误当然不是由这个类自身引起的.

      我觉得应用程序中的类从来不直接使用JavaBeans Introspector.所以他们一般不会导致内部查看资源泄露.但是一些类库和框架往往会产生这个问题.例如:Struts 和Quartz这一段可能就是我想寻找的答案,因为服务器仅仅是空载运行,我并没有在其中添加过多的服务器去消耗内存,那么原以为使用的几个框架是不会有内存泄漏这种问题的。而且当初也没怎么敢向这个方向考虑,但是Spring这份文档已经说明的比较清楚,刚好Struts和Quartz我都用到了,那么原因很有可能就处在这里。于是我就增加了这个IntrospectorLisener的监听器,期望有所收获。

 

=====================================================================================

开两个服务器,一个使用这个Lisener,另一个不使用,再同时运行24小时以后做比较,终于发现了喜人的成果:(一个是服务器,一个是我的办公用机,二者开销不大一样)

使用Lisener的服务器:

 

 

仍然是一个相对稳定的锯齿形状,而且Used heap size的峰值也没有超出启动时多少,让我们再看看没有使用Lisener是什么样子,这里我有个郁闷的问题,就是在长时间使用JProfiler之后,程序就会变得非常慢,CPU占用率直接100%,内存使用量也上升许多…… 可能是监控的程序泄漏太严重导致JProfiler也受影响

已经很明显了,没有用Lisener的服务器的内存用量明显呈上升趋势,然后再去查看Memory Views和Heap Walker,我这里会比较特殊,我想一般的程序级的内存泄漏早就可以暴露出来了,但是这里由于是框架上的问题,看了Memory Views和Heap我仍然没有很清晰的看到问题所在,因为数据显示占用量最大的是org.apache.catalina.startup.Bootstrap.main占用了最多的资源,这就涉及到更为底层的东西,我目前对Tomcat的运行机制还不是特别了解,但根据资料我猜测Tomcat加载的类最终都是交由Bootstrap来处理,相当于WebApp的根节点,那么占用最大资源也是在情理之中,而在Bootstrap之下加载上来的类也是Spring、Hibernate包下面的东西最多,那么这就符合最初的思路,在服务器运行过程中,Spring不停的运行的计划任务和OpenSessionInViewFilter,使得Tomcat反复加载对象而产生框架并用时可能产生的内存泄漏,则使用IntrospectorCleanupListener作为相应的解决办法。



   发表时间:2006-12-27  
好贴!
"在服务器运行过程中,Spring不停的运行的计划任务和OpenSessionInViewFilter,使得Tomcat反复加载对象而产生框架并用时可能产生的内存泄漏,则使用IntrospectorCleanupListener作为相应的解决办法。"

对于这一句话,引用关于IntrospectorCleanupListener一段解释:

引用
spring中的提供了一个名为org.springframework.web.util.IntrospectorCleanupListener的监听器。它主要负责处理由 JavaBeans Introspector的使用而引起的缓冲泄露。spring中对它的描述如下:它是一个在web应用关闭的时候,清除JavaBeans Introspector的监听器.web.xml中注册这个listener.可以保证在web 应用关闭的时候释放与掉这个web  应用相关的class loader 和由它管理的类如果你使用了JavaBeans Introspector来分析应用中的类,Introspector 缓冲中会保留这些类的引用.结果在你的应用关闭的时候,这些类以及web 应用相关的class loader没有被垃圾回收.不幸的是,清除Introspector的唯一方式是刷新整个缓冲.这是因为我们没法判断哪些是属于你的应用的引用.所以删除被缓冲的introspection会导致把这台电脑上的所有应用的introspection都删掉.需要注意的是,spring 托管的bean不需要使用这个监听器.因为spring它自己的introspection所使用的缓冲在分析完一个类之后会被马上从javaBeans Introspector缓冲中清除掉.应用程序中的类从来不直接使用JavaBeans Introspector.所以他们一般不会导致内部查看资源泄露.但是一些类库和框架往往会产生这个问题.例如:Struts 和Quartz.单个的内部查看泄漏会导致整个的web应用的类加载器不能进行垃圾回收.在web应用关闭之后,你会看到此应用的所有静态类资源(例如单例).这个错误当然不是由这个类自  身引起的.

用法很简单,就是在web.xml中加入:
    <listener>
        <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
    </listener>

0 请登录后投票
   发表时间:2006-12-27  
springside
工作组也有发现
0 请登录后投票
   发表时间:2006-12-27  
在我看来这算不上好贴,诚然你写这个帖子花了不少精力,不过帖子并没有真正找到出问题的原因。

在服务器空载的情况下还会导致服务器宕机或者重起,在此期间只有quartz的调度任务在跑,那么应该重点检查你的调度任务究竟执行了什么程序。

其实从你的JProfilter分析图看不出来任何有价值的东西,至于你提到的EHCache,是因为默认就启用的(你可以从配置里面关闭),Hibernate会默认维护两个Cache,一个TimeStamp,一个是Query。

OpenSessionInView在即使大型的应用程序当中也不会有什么性能问题,当你的quartz调用bean的时候,并不会走OpenSessionInView Filter,再加上你是空载状态跑,所以出问题基本可以排除OpenSessionInView。当然最后你自己也没有找到出问题的原因,根据我的推断,还是重点检查你的Quartz部分编写的代码。

对于Web容器来说,最忌讳应用程序私自启动线程,自行进行线程调度,像Quartz这种在web容器内部默认就自己启动了10线程进行异步job调度的框架本身就是很危险的事情,很容易造成servlet线程资源回收不掉,所以我一向排斥使用quartz。

建议当应用服务器宕机的时候,kill -3 pid杀一下,看看线程堆栈的dump信息,有没有大量线程被阻塞或者挂起的现象,另外就是查看一下服务器的日志,看看有没有什么发现。

0 请登录后投票
   发表时间:2006-12-27  
具体的问题我分析不出来,只知道servlet标准不允许在web容器内自行做线程管理,quartz的问题确实存在。
0 请登录后投票
   发表时间:2006-12-27  
一般会用什么来代替quartz?
我们正在想用quartz来代替orcle的job。。。看来还要再多试试
0 请登录后投票
   发表时间:2006-12-27  
我在最近的项目中使用了Quartz,
看到robbin的话,惊出一身冷汗,
那怎么来实现任务调度比较好呢?
0 请登录后投票
   发表时间:2006-12-27  
quartz还有一个问题就是不支持cluster。导致使用quartz的应用都没有办法做群集。

如果是我的话,我采取的办法就是自己单独启动一个Job Server,来跑job,不会部署在web容器中。

其他web节点当需要启动异步任务的时候,可以通过种种方式(DB, JMS, Web Service, etc)通知Job Server,而Job Server收到这个通知之后,把异步任务加载到自己的任务队列中去。

其实想改造当前已经集成quartz的web应用也不算困难:

例如可以使用数据库的表来记录和维护任务队列和状态,把quartz部分完全从web应用中剥离出去,自己写一个Java Main程序把配置quartz的spring容器跑起来,这样Job Server就启动了(注意这个Job Server完全脱离tomcat)。此外这个Main程序应该再启动一个子线程,定期扫描数据库的任务队列表:
有新的任务就加入quartz的任务调度;
把当前任务的执行状态写入任务表;
看到删除任务的表字段状态以后,删除相应的任务。

然后web应用去掉quartz部分配置,把原来的调用quartz任务的代码改写为读写数据库的任务表,这样就把job部分完全从web容器剥离掉了,甚至web容器做cluster也没有问题了,并且多个web节点在同时读写任务表的时候,还有数据库的事务来确保操作的一致性,实在是很棒。

另外还可以单独做一个job管理界面,可以通过web界面手工添加任务,查看任务状态,删除任务等等。

0 请登录后投票
   发表时间:2006-12-27  
Robbin回帖总是那么犀利,把这个帖子拿出来也就是希望大家能多提意见

针对Robbin的建议和做法,我有个问题:之所以将Quartz集成在Web容器,是因为这个服务本身就是Spring提供的,我只是调用一下,节省了很多代码,因为之前我也自己去写Job代码,但是那个控制起来相对麻烦不少。
与Web集成在一起使我的系统易于维护,而且资源都是共用的,因为起这个Quartz也就是为了对我的数据进行重新组织,那么Sping+Hibernate这部分业务代码都是统一的。
我没用过EJB,但是我知道EJB也提供Job、JMS这些服务,Spring容器也是为了提供这些服务才被选用到系统中,我想问如果我把系统中使用Job、JMS都分离出去单独重写,我还使用Spring做些什么呢?

另外IntrospectorCleanupListener是针对集成Struts、Quartz才使用的,即便我隔离Job服务也许用一下也是有必要的

还有Hibernate  OpenSessionInView 我认为这种将业务暴露在表现层的方法非常的不好,实际使用中性能也非常的不好控制,所以我个人是不会再支持使用,和他人无关
0 请登录后投票
   发表时间:2006-12-27  
robbin 写道

对于Web容器来说,最忌讳应用程序私自启动线程,自行进行线程调度,像Quartz这种在web容器内部默认就自己启动了10线程进行异步job调度的框架本身就是很危险的事情,很容易造成servlet线程资源回收不掉,所以我一向排斥使用quartz。


刚才在午睡的时候又考虑了一下,我想关于Web容器避免私自启动线程这个问题的确是由于我经验不足没有意识到这一点(其实在Web容器中私起线程也不是我发明的,去网上找找就有许许多多的例子,我也是得到他人指点才这样使用),我在后续的一个系统中采用了同样的Job任务方式,其中使用到了SNMP服务,这个东西使用到了大量的线程和端口,使得服务器非常的不稳定,后来的解决方式就是将这个SNMP服务作为一个单独的Web服务。
Robbin的方法更好一些,我们的系统并不涉及到集群的问题,所以以前并没有考虑过。
1 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics