本文來自于HeapDump性能社區(qū)! !有性能問題掠归,上HeapDump性能社區(qū)!
正文:
Java 應用程序只允許使用有限的內存量悄泥。您的特定應用程序可以使用的確切內存量是在應用程序啟動期間指定的虏冻。為了讓事情變得更復雜,Java 內存被分成不同的區(qū)域弹囚,如下圖所示:
所有這些區(qū)域的大小厨相,包括 permgen 區(qū)域,都是在 JVM 啟動期間設置的鸥鹉。如果您不自己設置大小蛮穿,將使用特定于平臺的默認值。
該java.lang.OutOfMemoryError:PermGen space的消息表明永久代的內存區(qū)域被耗盡毁渗。
1践磅,什么原因造成的?
要了解java.lang.OutOfMemoryError: PermGen space 的原因灸异,我們需要了解此特定內存區(qū)域的用途府适。
出于實際目的羔飞,永久代主要由加載并存儲到 PermGen 中的類聲明組成。這包括類的名稱和字段檐春、帶有方法字節(jié)碼的方法逻淌、常量池信息、與類關聯(lián)的對象數(shù)組和類型數(shù)組以及即時編譯器優(yōu)化疟暖。
從上面的定義中卡儒,您可以推斷出永久代的大小要求取決于加載的類的數(shù)量以及此類聲明的大小。因此俐巴,我們可以說*java.lang.OutOfMemoryError: PermGen space 的*****主要原因是加載到永久代的類太多或類太大骨望。
2,舉個例子
如上所述窜骄,永久代空間的使用與加載到 JVM 中的類數(shù)量密切相關锦募。下面的代碼是最直接
導入 javassist.ClassPool;
public class MicroGenerator {
public static void main(String[] args) 拋出異常 {
for (int i = 0; i < 100_000_000; i++) {
generate("eu.plumbr.demo.Generated" + i);
}
}
public static Class generate(String name) throws Exception {
ClassPool pool = ClassPool.getDefault();
返回 pool.makeClass(name).toClass();
}
}
在這個例子中摆屯,源代碼遍歷一個循環(huán)并在運行時生成類邻遏。javassist庫負責處理類生成的復雜性。
啟動上面的代碼將繼續(xù)生成新類并將它們的定義加載到永久空間中虐骑,直到空間被完全利用并拋出java.lang.OutOfMemoryError: Permgen 空間准验。
重新部署時間示例
對于更復雜和更現(xiàn)實的示例,讓我們帶您了解在應用程序重新部署期間發(fā)生的java.lang.OutOfMemoryError: Permgen space錯誤廷没。當您重新部署應用程序時糊饱,您會期望垃圾回收將擺脫引用所有先前加載的類的先前類加載器,并將其替換為加載類的新版本的類加載器颠黎。
不幸的是另锋,許多 3rd 方庫和對資源(例如線程、JDBC 驅動程序或文件系統(tǒng)句柄)的處理不當使得無法卸載以前使用的類加載器狭归。這反過來意味著在每次重新部署期間夭坪,您的類的所有先前版本仍將駐留在 PermGen 中,在每次重新部署期間生成數(shù)十兆字節(jié)的垃圾过椎。
讓我們想象一個使用 JDBC 驅動程序連接到關系數(shù)據(jù)庫的示例應用程序室梅。當應用程序啟動時,初始化代碼加載 JDBC 驅動程序以連接到數(shù)據(jù)庫疚宇。對應于規(guī)范亡鼠,JDBC 驅動程序使用java.sql.DriverManager注冊自己。此注冊包括在DriverManager的靜態(tài)字段中存儲對驅動程序實例的引用敷待。
現(xiàn)在间涵,當應用程序從應用程序服務器中卸載時,java.sql.DriverManager仍將保留該引用榜揖。我們最終獲得了對驅動程序類的實時引用勾哩,該類又包含對用于加載應用程序的java.lang.Classloader實例的引用股耽。這反過來意味著垃圾收集算法無法回收空間。
并且java.lang.ClassLoader 的那個實例仍然引用應用程序的所有類钳幅,通常在 PermGen 中占用數(shù)十兆字節(jié)物蝙。這意味著只需重新部署幾次即可填充通常大小的 PermGen 并在日志中獲取java.lang.OutOfMemoryError: PermGen space錯誤消息。
3敢艰,解決辦法是什么诬乞?
1.解決初始化時OutOfMemoryError
當應用程序啟動時觸發(fā)由于 PermGen 耗盡導致的 OutOfMemoryError 時,解決方案很簡單钠导。應用程序只需要更多空間將所有類加載到 PermGen 區(qū)域震嫉,所以我們只需要增加它的大小。為此牡属,請更改您的應用程序啟動配置并添加(或增加(如果存在))類似于以下示例的-XX:MaxPermSize參數(shù):
java -XX:MaxPermSize=512m com.yourcompany.YourClass
上述配置將告訴 JVM票堵,允許 PermGen 增長到 512MB,然后才能開始以 OutOfMemoryError 的形式抱怨逮栅。
2.解決重新部署時OutOfMemoryError
當您重新部署應用程序后立即發(fā)生 OutOfMemoryError 時悴势,您的應用程序會遭受類加載器泄漏。在這種情況下措伐,解決問題的最簡單特纤,最直接的方式就是用工具排查,找到有問題的代碼侥加,并解決它以分鐘為單位捧存。
對于那些不能使用 Plumbr 或決定不使用的人,也可以使用替代方法担败。為此昔穴,您應該繼續(xù)進行堆轉儲分析 - 在重新部署后使用類似于以下命令的命令進行堆轉儲:
jmap -dump:format=b,file=dump.hprof <process-id>
然后使用您最喜歡的堆轉儲分析器打開轉儲(Eclipse MAT 是一個很好的工具)。在分析器中提前,您可以查找重復的類吗货,尤其是那些加載應用程序類的類。從那里岖研,您需要進入所有類加載器以找到當前活動的類加載器卿操。
對于不活動的類加載器,您需要通過從不活動的類加載器獲取到GC 根的最短路徑來確定阻止它們被垃圾收集的引用孙援。有了這些信息害淤,您就會找到根本原因。如果根本原因在 3rd 方庫中拓售,您可以繼續(xù)訪問 Google/StackOverflow 以查看這是否是獲取補丁/解決方法的已知問題窥摄。如果這是您自己的代碼,則需要刪除違規(guī)引用础淤。
3.解決運行時OutOfMemoryError
對于那些再次無法使用 Plumbr 的人崭放,也可以使用另一種方法哨苛。在這種情況下,第一步是檢查是否允許 GC 從 PermGen 卸載類币砂。標準的 JVM 在這方面相當保守——類天生就是為了永生建峭。所以一旦加載,即使沒有代碼再使用它們决摧,類也會留在內存中亿蒸。當應用程序動態(tài)創(chuàng)建大量類并且長時間不需要生成的類時,這可能會成為一個問題掌桩。在這種情況下边锁,允許 JVM 卸載類定義會很有幫助。這可以通過向啟動腳本添加一個配置參數(shù)來實現(xiàn):
-XX:+CMSClassUnloadingEnabled
默認情況下波岛,它設置為 false茅坛,因此要啟用它,您需要在 Java 選項中顯式設置以下選項则拷。如果您啟用CMSClassUnloadingEnabled贡蓖,GC 也會清除PermGen 并刪除不再使用的類。請記住隔躲,此選項僅在使用以下選項啟用UseConcMarkSweepGC時才有效摩梧。因此物延,當運行ParallelGC或宣旱,上帝保佑,Serial GC 時叛薯,請確保您已通過指定將 GC 設置為CMS:
-XX:+UseConcMarkSweepGC
在確被胍鳎可以卸載類并且問題仍然存在后,您應該繼續(xù)進行堆轉儲分析 - 使用類似于以下的命令進行堆轉儲:
jmap -dump:file=dump.hprof,format=b <process-id>
然后使用您最喜歡的堆轉儲分析器(例如 Eclipse MAT)打開轉儲耗溜,并根據(jù)加載的類數(shù)量繼續(xù)查找最昂貴的類加載器组力。從這樣的類加載器中,您可以繼續(xù)提取加載的類并按實例對此類類進行排序抖拴,以獲得可疑的頂部列表燎字。
對于每個嫌疑人,您需要手動將根本原因追溯到生成此類類的應用程序代碼阿宅。
Java OOM系列專題:
第一篇:Java OOM 原理篇 : 什么是 Java OOM
第二篇:Java OOM 基礎篇:常見的OutOfMemoryError 場景一:Java heap space 堆溢出問題詳解
第三篇:Java OOM 基礎篇:常見的OutOfMemoryError 場景二 : GC overhead limit exceeded 問題詳解
第四篇:Java OOM 基礎篇:常見的OutOfMemoryError 場景三: PermGen space 永久空間問題詳解
第五篇:Java OOM 基礎篇:常見的OutOfMemoryError 場景四: Permgen size 元空間問題詳解
第六篇:Java OOM 實戰(zhàn)篇:應用故障之Java heap space 堆溢出實戰(zhàn)