原文連接:http://nius.me/classloader-memory-leak/html
對於j2ee項目,一直使用eclipse的wtp,每次修改代碼後,能夠自動熱部署。簡單的項目wtp彷佛沒什麼問題,但一旦項目代碼稍微多一點,就很容易出現各類莫名其妙掉掛的現象,不得不整個重啓tomcat服務器,這個時候就很痛苦了。java
因而,我換用了maven的jetty插件,啓動一個輕量級的jetty服務器,這下熱部署彷佛沒那麼多問題了!可是jetty彷佛在熱部署幾回以後,也仍然會崩潰!這是什麼狀況!tomcat和jetty到底發生了什麼?jetty的崩潰最常看見的異常就是OutOfMemoryException,Perm區的內存佔滿了。git
不要慌!爲了節省時間,先上解決方案:classloader-leak-prevention。github
<dependency> <groupId>se.jiderhamn</groupId> <artifactId>classloader-leak-prevention</artifactId> <version>1.9.3</version> </dependency>
<listener> <listener-class> se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor </listener-class> </listener>
今天實在忍不了了,上Visual VM,監視一下Perm區的狀況。
果真,reload幾回以後,Perm的使用量蹭蹭的往上漲。看看Perm區都是啥
怎麼這麼多char[]呀,好像也看不出什麼,仍是dump一下吧。中間翻閱了一些博客,以爲應該是class loader的問題。oql查詢一下。(這裏由於是jetty,因此查詢了jetty的WebAppClassLoader,若是使用tomcat或者其餘容器,應該有對應的Loader,能夠經過Saved Queries->PermGen Analysis->ClassLoader Types看一下有哪些ClassLoader)web
上圖是剛啓動的時候,WebAppClassLoader只有一個實例。通過兩次reload,嘿,問題來了。以下圖:sql
竟然出現了3個WebAppClassLoader實例,前兩個Loader都沒有銷燬!喲呵呵,gc銷不掉,一會就進Perm區了,而後多幾回reload,Perm區再大也都撐滿了。tomcat
首先,咱們回憶一下gc的運做過程。經過minor gc,清理eden區(eden generation)的沒有被引用的對象,活下來的進入suvivor區,接下來的minor gc會讓對象在兩個suvivor裏面倒騰倒騰,挺過幾回的進入old區,這裏面進行的就是full gc了,耗時長,若是old區還挺了好幾回,就會進入Perm區。Perm裏面發生的也是full gc.服務器
一個普通對象,只要沒有引用了,就必定會在某一次gc被回收。那麼ClassLoader進入了Perm,說明在reload的時候,雖然程序取消了對Classloader的直接引用,可是仍然有其餘對象間接的引用了ClassLoader。其實若是單純的僅僅是ClassLoader一個對象,也就罷了,可是ClassLoader並非一個普通的對象。less
任何一個Java類,都是經過某個ClassLoader加載的。經過這個ClassLoader加載的類的實例,會保存這個類對象的引用,也就是MyClass.class這種。而類對象,會保留這個ClassLoader的引用。反過來,在ClassLoader中,也會保持對這個類對象的引用。(注意區分類對象MyClass.class,不是這個類的實例。好吧若是仍是混淆了,我也不知道該怎麼說清楚了)。關鍵在於,ClassLoader和類對象之間是雙向引用。eclipse
雙向引用有什麼問題嘛?通常狀況下沒有問題。由於若是ClassLoader的外界引用,和具體類對象的外界引用都消失了,那麼這兩個對象都不可達了,都會被gc。可是在一些狀況下,類對象可能不單單被這個類的實例保存,還可能被其餘對象保存!若是這個對象是其餘OtherClassLoader加載的呢?那意味着,若是這個對象不回收,那麼其引用的類對象不會被回收,因而ClassLoader不會被回收,因而,全部ClassLoader加載的類對象都不會被回收!WebAppClassLoader會加載多少個類?若是你剛好使用的是Spring、Hibernate這種你們夥,嚯嚯。若是對此很感興趣,這裏有一篇寫的很詳細的:Anatomy of a PermGen Memory Leak
你並不知道何時會出現某個外部對象會引用到類對象。因此解決問題的思路是,換一個ClassLoader。一開始的解決方案classloader-leak-prevention就是依賴這個思路的。核心代碼以下:
// Switch to system classloader in before we load/call some JRE stuff that will cause // the current classloader to be available for garbage collection Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader()); try { java.awt.Toolkit.getDefaultToolkit(); // Will start a Thread } catch (Throwable t) { error(t); warn("Consider adding -Djava.awt.headless=true to your JVM parameters"); } java.security.Security.getProviders(); java.sql.DriverManager.getDrivers(); // Load initial drivers using system classloader javax.imageio.ImageIO.getCacheDirectory(); // Will call sun.awt.AppContext.getAppContext()
讓這一段代碼運行在servlet初始化以前,在全部的listener以前。
下面是本身翻閱的一些資料:
英文的資料:http://java.jiderhamn.se/2012/03/04/classloader-leaks-vi-this-means-war-leak-prevention-library/ (在此連接可找到最新的maven地址以及源碼地址以及非maven得jar)
Git源碼地址:https://github.com/mjiderhamn/classloader-leak-prevention