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

来源:互联网 发布:c语言贪吃蛇小游戏 编辑:程序博客网 时间:2024/04/30 19:55

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

       把代码拿回来找原因,用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作为相应的解决办法。

原创粉丝点击