1. 程式人生 > >整理SSH框架容易出現的記憶體洩漏情況

整理SSH框架容易出現的記憶體洩漏情況

前言

最近幾周,一直忙著處理上屆畢業學長的遺留專案問題,一個基於Spring-Struts-Hibernate框架的網站系統。上線那邊隔幾天系統就崩潰一次,真是弄得人心惶惶,終於測試人員還是發現了報錯的log…OOM(Out Of Memory,記憶體溢位),通俗的理解一下,大概就是記憶體不夠用了,看了人家的工作管理員,很強…伺服器佔了17G記憶體…總共是64G…達到Tomcat預設設定的所謂的本機記憶體的1/4了已經。
java
OOM
這是當時的報錯資訊,我的理解就是,記憶體因為某種原因,不夠用了,所以系統崩潰…
同學給了幾種可能的情況:1.jvm小(17G,否了)。2.觸發死迴圈(網站很難出現死迴圈吧…)。3.執行緒回收機制有問題,無法回收(誒!就是這個!)。
經過多方查詢資料,我們這個系統,應該就是記憶體洩漏導致的記憶體溢位現象

記憶體洩漏是什麼?

    記憶體洩漏(Memory Leak)是指程式中己動態分配的堆記憶體由於某種原因程式未釋放或無法釋放,造成系統記憶體的浪費,導致程式執行速度減慢甚至系統崩潰等嚴重後果。

作為一個網站專案,而且存在動態分配的記憶體,那麼我暫時只想得到定時器這個東西。
本專案中,用了兩個不同的定時器,TimertaskQuartz

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";