前言
最近几周,一直忙着处理上届毕业学长的遗留项目问题,一个基于Spring-Struts-Hibernate框架的网站系统。上线那边隔几天系统就崩溃一次,真是弄得人心惶惶,终于测试人员还是发现了报错的log…OOM(Out Of Memory,内存溢出),通俗的理解一下,大概就是内存不够用了,看了人家的任务管理器,很强…服务器占了17G内存…总共是64G…达到Tomcat默认设置的所谓的本机内存的1/4了已经。
这是当时的报错信息,我的理解就是,内存因为某种原因,不够用了,所以系统崩溃…
同学给了几种可能的情况:1.jvm小(17G,否了)。2.触发死循环(网站很难出现死循环吧…)。3.线程回收机制有问题,无法回收(诶!就是这个!)。
经过多方查找资料,我们这个系统,应该就是内存泄漏导致的内存溢出现象。
内存泄漏是什么?
内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
作为一个网站项目,而且存在动态分配的内存,那么我暂时只想得到定时器这个东西。
本项目中,用了两个不同的定时器,Timertask和Quartz。
Timertask是JDK自带的定时器工具,于情于理,我觉得问题不在这。
Quartz是一个开源作业调度框架框架,框架结合着用很容易出问题…
两种内存泄漏的可能
在Spring框架中使用Quartz作业调度框架
由Spring官方API指出:
/**
* Listener that flushes the JDK's {@link java.beans.Introspector JavaBeans Introspector}
* cache on web app shutdown. Register this listener in your <code>web.xml</code> to
* guarantee proper release of the web application class loader and its loaded classes.
*
* <p><b>If the JavaBeans Introspector has been used to analyze application classes,
* the system-level Introspector cache will hold a hard reference to those classes.
* Consequently, those classes and the web application class loader will not be
* garbage-collected on web app shutdown!</b> This listener performs proper cleanup,
* to allow for garbage collection to take effect.
*
* <p>Unfortunately, the only way to clean up the Introspector is to flush
* the entire cache, as there is no way to specifically determine the
* application's classes referenced there. This will remove cached
* introspection results for all other applications in the server too.
*
* <p>Note that this listener is <i>not</i> necessary when using Spring's beans
* infrastructure within the application, as Spring's own introspection results
* cache will immediately flush an analyzed class from the JavaBeans Introspector
* cache and only hold a cache within the application's own ClassLoader.
*
* <b>Although Spring itself does not create JDK Introspector leaks, note that this
* listener should nevertheless be used in scenarios where the Spring framework classes
* themselves reside in a 'common' ClassLoader (such as the system ClassLoader).</b>
* In such a scenario, this listener will properly clean up Spring's introspection cache.
*
* <p>Application classes hardly ever need to use the JavaBeans Introspector
* directly, so are normally not the cause of Introspector resource leaks.
* Rather, many libraries and frameworks do not clean up the Introspector:
* e.g. Struts and Quartz.
*
* <p>Note that a single such Introspector leak will cause the entire web
* app class loader to not get garbage collected! This has the consequence that
* you will see all the application's static class resources (like singletons)
* around after web app shutdown, which is not the fault of those classes!
*
* <p><b>This listener should be registered as the first one in <code>web.xml</code>,
* before any application listeners such as Spring's ContextLoaderListener.</b>
* This allows the listener to take full effect at the right time of the lifecycle.
*
* @author Juergen Hoeller
* @since 1.1
* @see java.beans.Introspector#flushCaches()
* @see org.springframework.beans.CachedIntrospectionResults#acceptClassLoader
* @see org.springframework.beans.CachedIntrospectionResults#clearClassLoader
*/
大概意思就是
/**
* IntrospectorCleanupListener,这个监听器的作用及用法
* -------------------------------------------------------------------------------------------------------------------
* 此监听器主要为了解决java.beans.Introspector导致内存泄漏的问题
* 此监听器必须配置在web.xml中与Spring相关监听器中的第一个位置(也要在ContextLoaderListener的前面)
* -------------------------------------------------------------------------------------------------------------------
* 如果有的框架或程序用到了java.beans.Introspector类,那么就会启用一个系统级的缓存,缓存中会存放一些加载并分析过的JavaBean的引用
* 当Web服务器关闭时,由于缓存中存放着这些JavaBean的引用,所以垃圾回收器无法回收Web容器中的JavaBean对象,最后导致内存泄漏
* -------------------------------------------------------------------------------------------------------------------
* 而org.springframework.web.util.IntrospectorCleanupListener就是专门用来处理Introspector内存泄漏问题的监听器
* 将IntrospectorCleanupListener配置在监听器的第一个位置,提前监听到那些不会自动清理的内存空间
* 如此IntrospectorCleanupListener便会注意清理Introspector缓存,使那些Javabean能被垃圾回收器正确回收
* -------------------------------------------------------------------------------------------------------------------
* Spring在加载并分析完一个类之后会马上刷新Introspector缓存,所以Spring本身没有内存泄漏问题
* 但有些框架在使用了Introspector之后,自己不会进行清理工作(如Quartz,Struts),Spring也不会回收他们的内存,最后导致内存泄漏
* -------------------------------------------------------------------------------------------------------------------
* @create Nov 16, 2018 12:41
* @author 洛北辰南
*/
<listener>
<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
以上,将监听器加入web.xml,并配置在第一个,即可解决quartz的内存泄漏问题。
Hibernate使用拼接HQL
这个问题,我这里暂时还没有细看,是偶然搜到的一种内存泄漏的可能性,在此只是简单说一下原理跟解决方案。
Hibernate的QueryPlanCache会缓存hql语句,以免于之后再有相同的hql重复编译。
如果hql语句中使用in,并且in后参数不同,那么就会作为不同的语句都进行缓存。
如果hql语句使用拼接的方式,每次都会缓存一条hql,导致缓存原来越大,这部分内存也不会清理,最后就会溢出。
//拼接hql
String hql = "select u.name from userinfo u where id = " + id;
但若是改成这样,通过占位符传参,hibernate就只会缓存一条hql,缓存就不会一直增长,最后溢出。
//占位符传参
String hql = "select u.name from userinfo u where id = :id";