常見(jiàn)OOM問(wèn)題之PermGen space 永久空間問(wèn)題詳解

本文來(lái)自于HeapDump性能社區(qū)! !有性能問(wèn)題,上HeapDump性能社區(qū)!

正文:

Java 應(yīng)用程序只允許使用有限的內(nèi)存量。您的特定應(yīng)用程序可以使用的確切內(nèi)存量是在應(yīng)用程序啟動(dòng)期間指定的。為了讓事情變得更復(fù)雜,Java 內(nèi)存被分成不同的區(qū)域,如下圖所示:

v2-acc3571aee7060370c492e0f8d4f69fa_1440w.png

所有這些區(qū)域的大小,包括 permgen 區(qū)域,都是在 JVM 啟動(dòng)期間設(shè)置的。如果您不自己設(shè)置大小,將使用特定于平臺(tái)的默認(rèn)值。

java.lang.OutOfMemoryError:PermGen space的消息表明永久代的內(nèi)存區(qū)域被耗盡。

1,什么原因造成的?

要了解java.lang.OutOfMemoryError: PermGen space 的原因,我們需要了解此特定內(nèi)存區(qū)域的用途。

出于實(shí)際目的,永久代主要由加載并存儲(chǔ)到 PermGen 中的類(lèi)聲明組成。這包括類(lèi)的名稱(chēng)和字段、帶有方法字節(jié)碼的方法、常量池信息、與類(lèi)關(guān)聯(lián)的對(duì)象數(shù)組和類(lèi)型數(shù)組以及即時(shí)編譯器優(yōu)化。

從上面的定義中,您可以推斷出永久代的大小要求取決于加載的類(lèi)的數(shù)量以及此類(lèi)聲明的大小。因此,我們可以說(shuō)*java.lang.OutOfMemoryError: PermGen space 的*****主要原因是加載到永久代的類(lèi)太多或類(lèi)太大。

2,舉個(gè)例子

如上所述,永久代空間的使用與加載到 JVM 中的類(lèi)數(shù)量密切相關(guān)。下面的代碼是最直接

導(dǎo)入 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(); 
  } 
}

在這個(gè)例子中,源代碼遍歷一個(gè)循環(huán)并在運(yùn)行時(shí)生成類(lèi)。javassist庫(kù)負(fù)責(zé)處理類(lèi)生成的復(fù)雜性。

啟動(dòng)上面的代碼將繼續(xù)生成新類(lèi)并將它們的定義加載到永久空間中,直到空間被完全利用并拋出java.lang.OutOfMemoryError: Permgen 空間。

重新部署時(shí)間示例

對(duì)于更復(fù)雜和更現(xiàn)實(shí)的示例,讓我們帶您了解在應(yīng)用程序重新部署期間發(fā)生的java.lang.OutOfMemoryError: Permgen space錯(cuò)誤。當(dāng)您重新部署應(yīng)用程序時(shí),您會(huì)期望垃圾回收將擺脫引用所有先前加載的類(lèi)的先前類(lèi)加載器,并將其替換為加載類(lèi)的新版本的類(lèi)加載器。

不幸的是,許多 3rd 方庫(kù)和對(duì)資源(例如線程、JDBC 驅(qū)動(dòng)程序或文件系統(tǒng)句柄)的處理不當(dāng)使得無(wú)法卸載以前使用的類(lèi)加載器。這反過(guò)來(lái)意味著在每次重新部署期間,您的類(lèi)的所有先前版本仍將駐留在 PermGen 中,在每次重新部署期間生成數(shù)十兆字節(jié)的垃圾。

讓我們想象一個(gè)使用 JDBC 驅(qū)動(dòng)程序連接到關(guān)系數(shù)據(jù)庫(kù)的示例應(yīng)用程序。當(dāng)應(yīng)用程序啟動(dòng)時(shí),初始化代碼加載 JDBC 驅(qū)動(dòng)程序以連接到數(shù)據(jù)庫(kù)。對(duì)應(yīng)于規(guī)范,JDBC 驅(qū)動(dòng)程序使用java.sql.DriverManager注冊(cè)自己。此注冊(cè)包括在DriverManager的靜態(tài)字段中存儲(chǔ)對(duì)驅(qū)動(dòng)程序?qū)嵗囊谩?/p>

現(xiàn)在,當(dāng)應(yīng)用程序從應(yīng)用程序服務(wù)器中卸載時(shí),java.sql.DriverManager仍將保留該引用。我們最終獲得了對(duì)驅(qū)動(dòng)程序類(lèi)的實(shí)時(shí)引用,該類(lèi)又包含對(duì)用于加載應(yīng)用程序的java.lang.Classloader實(shí)例的引用。這反過(guò)來(lái)意味著垃圾收集算法無(wú)法回收空間。

并且java.lang.ClassLoader 的那個(gè)實(shí)例仍然引用應(yīng)用程序的所有類(lèi),通常在 PermGen 中占用數(shù)十兆字節(jié)。這意味著只需重新部署幾次即可填充通常大小的 PermGen 并在日志中獲取java.lang.OutOfMemoryError: PermGen space錯(cuò)誤消息。

3,解決辦法是什么?

1.解決初始化時(shí)OutOfMemoryError

當(dāng)應(yīng)用程序啟動(dòng)時(shí)觸發(fā)由于 PermGen 耗盡導(dǎo)致的 OutOfMemoryError 時(shí),解決方案很簡(jiǎn)單。應(yīng)用程序只需要更多空間將所有類(lèi)加載到 PermGen 區(qū)域,所以我們只需要增加它的大小。為此,請(qǐng)更改您的應(yīng)用程序啟動(dòng)配置并添加(或增加(如果存在))類(lèi)似于以下示例的-XX:MaxPermSize參數(shù):

java -XX:MaxPermSize=512m com.yourcompany.YourClass

上述配置將告訴 JVM,允許 PermGen 增長(zhǎng)到 512MB,然后才能開(kāi)始以 OutOfMemoryError 的形式抱怨。

2.解決重新部署時(shí)OutOfMemoryError

當(dāng)您重新部署應(yīng)用程序后立即發(fā)生 OutOfMemoryError 時(shí),您的應(yīng)用程序會(huì)遭受類(lèi)加載器泄漏。在這種情況下,解決問(wèn)題的最簡(jiǎn)單,最直接的方式就是用工具排查,找到有問(wèn)題的代碼,并解決它以分鐘為單位。

對(duì)于那些不能使用 Plumbr 或決定不使用的人,也可以使用替代方法。為此,您應(yīng)該繼續(xù)進(jìn)行堆轉(zhuǎn)儲(chǔ)分析 - 在重新部署后使用類(lèi)似于以下命令的命令進(jìn)行堆轉(zhuǎn)儲(chǔ):

jmap -dump:format=b,file=dump.hprof <process-id>

然后使用您最喜歡的堆轉(zhuǎn)儲(chǔ)分析器打開(kāi)轉(zhuǎn)儲(chǔ)(Eclipse MAT 是一個(gè)很好的工具)。在分析器中,您可以查找重復(fù)的類(lèi),尤其是那些加載應(yīng)用程序類(lèi)的類(lèi)。從那里,您需要進(jìn)入所有類(lèi)加載器以找到當(dāng)前活動(dòng)的類(lèi)加載器。

對(duì)于不活動(dòng)的類(lèi)加載器,您需要通過(guò)從不活動(dòng)的類(lèi)加載器獲取到GC 根的最短路徑來(lái)確定阻止它們被垃圾收集的引用。有了這些信息,您就會(huì)找到根本原因。如果根本原因在 3rd 方庫(kù)中,您可以繼續(xù)訪問(wèn) Google/StackOverflow 以查看這是否是獲取補(bǔ)丁/解決方法的已知問(wèn)題。如果這是您自己的代碼,則需要?jiǎng)h除違規(guī)引用。

3.解決運(yùn)行時(shí)OutOfMemoryError

對(duì)于那些再次無(wú)法使用 Plumbr 的人,也可以使用另一種方法。在這種情況下,第一步是檢查是否允許 GC 從 PermGen 卸載類(lèi)。標(biāo)準(zhǔn)的 JVM 在這方面相當(dāng)保守——類(lèi)天生就是為了永生。所以一旦加載,即使沒(méi)有代碼再使用它們,類(lèi)也會(huì)留在內(nèi)存中。當(dāng)應(yīng)用程序動(dòng)態(tài)創(chuàng)建大量類(lèi)并且長(zhǎng)時(shí)間不需要生成的類(lèi)時(shí),這可能會(huì)成為一個(gè)問(wèn)題。在這種情況下,允許 JVM 卸載類(lèi)定義會(huì)很有幫助。這可以通過(guò)向啟動(dòng)腳本添加一個(gè)配置參數(shù)來(lái)實(shí)現(xiàn):

-XX:+CMSClassUnloadingEnabled

默認(rèn)情況下,它設(shè)置為 false,因此要啟用它,您需要在 Java 選項(xiàng)中顯式設(shè)置以下選項(xiàng)。如果您啟用CMSClassUnloadingEnabledGC 也會(huì)清除PermGen 并刪除不再使用的類(lèi)。請(qǐng)記住,此選項(xiàng)僅在使用以下選項(xiàng)啟用UseConcMarkSweepGC時(shí)才有效。因此,當(dāng)運(yùn)行ParallelGC或,上帝保佑,Serial GC 時(shí),請(qǐng)確保您已通過(guò)指定將 GC 設(shè)置為CMS

-XX:+UseConcMarkSweepGC

在確??梢孕遁d類(lèi)并且問(wèn)題仍然存在后,您應(yīng)該繼續(xù)進(jìn)行堆轉(zhuǎn)儲(chǔ)分析 - 使用類(lèi)似于以下的命令進(jìn)行堆轉(zhuǎn)儲(chǔ):

jmap -dump:file=dump.hprof,format=b <process-id>

然后使用您最喜歡的堆轉(zhuǎn)儲(chǔ)分析器(例如 Eclipse MAT)打開(kāi)轉(zhuǎn)儲(chǔ),并根據(jù)加載的類(lèi)數(shù)量繼續(xù)查找最昂貴的類(lèi)加載器。從這樣的類(lèi)加載器中,您可以繼續(xù)提取加載的類(lèi)并按實(shí)例對(duì)此類(lèi)類(lèi)進(jìn)行排序,以獲得可疑的頂部列表。

對(duì)于每個(gè)嫌疑人,您需要手動(dòng)將根本原因追溯到生成此類(lèi)類(lèi)的應(yīng)用程序代碼。

Java OOM系列專(zhuān)題:

第一篇:Java OOM 原理篇 : 什么是 Java OOM

第二篇:Java OOM 基礎(chǔ)篇:常見(jiàn)的OutOfMemoryError 場(chǎng)景一:Java heap space 堆溢出問(wèn)題詳解

第三篇:Java OOM 基礎(chǔ)篇:常見(jiàn)的OutOfMemoryError 場(chǎng)景二 : GC overhead limit exceeded 問(wèn)題詳解

第四篇:Java OOM 基礎(chǔ)篇:常見(jiàn)的OutOfMemoryError 場(chǎng)景三: PermGen space 永久空間問(wèn)題詳解

第五篇:Java OOM 基礎(chǔ)篇:常見(jiàn)的OutOfMemoryError 場(chǎng)景四: Permgen size 元空間問(wèn)題詳解

第六篇:Java OOM 實(shí)戰(zhàn)篇:應(yīng)用故障之Java heap space 堆溢出實(shí)戰(zhàn)

第七篇:Java OOM 高級(jí)篇:體驗(yàn)了一把線上CPU100%及應(yīng)用OOM的排查和解決過(guò)程

第八篇:Java OOM 高級(jí)篇:線上Docker 上Springboot程序OOM問(wèn)題的排查分享

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容