OutOfMemoryError系列(3): Permgen space

說(shuō)明:?Permgen(永久代) 屬于 JDK1.7 及之前版本的概念; 為了適應(yīng)Java程序的發(fā)展, JDK8以后的版本采用限制更少的 MetaSpace 來(lái)代替, 詳情請(qǐng)參考下一篇文章:?OutOfMemoryError系列(4): Metaspace逃沿。

這是本系列的第三篇文章, 相關(guān)文章列表:

OutOfMemoryError系列(1): Java heap space

OutOfMemoryError系列(2): GC overhead limit exceeded

OutOfMemoryError系列(3): Permgen space

OutOfMemoryError系列(4): Metaspace

JVM限制了Java程序的最大內(nèi)存使用量, 可以通過(guò)啟動(dòng)參數(shù)來(lái)配置萍丐。而Java的堆內(nèi)存被劃分為多個(gè)區(qū)域, 如下圖所示:

這些區(qū)域的最大值, 由JVM啟動(dòng)參數(shù)?-Xmx?和?-XX:MaxPermSize?指定. 如果沒(méi)有明確指定, 則根據(jù)操作系統(tǒng)平臺(tái)和物理內(nèi)存的大小來(lái)確定。

java.lang.OutOfMemoryError: PermGen space?錯(cuò)誤信息所表達(dá)的意思是:?永久代(Permanent Generation) 內(nèi)存區(qū)域已滿

原因分析

我們先看看?PermGen?是用來(lái)干什么的计技。

在JDK1.7及之前的版本, 永久代(permanent generation) 主要用于存儲(chǔ)加載/緩存到內(nèi)存中的 class 定義, 包括 class 的 名稱(chēng)(name), 字段(fields), 方法(methods)和字節(jié)碼(method bytecode); 以及常量池(constant pool information); 對(duì)象數(shù)組(object arrays)/類(lèi)型數(shù)組(type arrays)所關(guān)聯(lián)的 class, 還有 JIT 編譯器優(yōu)化后的class信息等晒衩。

很容易看出, PermGen 的使用量和JVM加載到內(nèi)存中的 class 數(shù)量/大小有關(guān)嗤瞎∏酵幔可以說(shuō)?java.lang.OutOfMemoryError: PermGen space?的主要原因, 是加載到內(nèi)存中的 class 數(shù)量太多或體積太大。

示例

最簡(jiǎn)單的例子

我們知道, PermGen 空間的使用量, 與JVM加載的 class 數(shù)量有很大關(guān)系猫胁。下面的代碼演示了這種情況:

importjavassist.ClassPool;publicclassMicroGenerator{publicstaticvoidmain(String[] args)throwsException {for(inti =0; i <100_000_000; i++) {? ? ? generate("eu.plumbr.demo.Generated"+ i);? ? }? }publicstaticClassgenerate(String name)throwsException {? ? ClassPool pool = ClassPool.getDefault();returnpool.makeClass(name).toClass();? }}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

這段代碼在 for 循環(huán)中, 動(dòng)態(tài)生成了很多class箱亿□诵浚可以看到, 使用?javassist?工具類(lèi)生成 class 是非常簡(jiǎn)單的弃秆。

執(zhí)行這段代碼, 會(huì)生成很多新的 class 并將其加載到內(nèi)存中, 隨著生成的class越來(lái)越多,將會(huì)占滿Permgen空間, 然后拋出?java.lang.OutOfMemoryError: Permgen space?錯(cuò)誤, 當(dāng)然, 也有可能會(huì)拋出其他類(lèi)型的 OutOfMemoryError。

要快速看到效果, 可以加上適當(dāng)?shù)腏VM啟動(dòng)參數(shù), 如:?-Xmx200M -XX:MaxPermSize=16M?等等髓帽。

Redeploy 時(shí)產(chǎn)生的 OutOfMemoryError

說(shuō)明:?如果在開(kāi)發(fā)時(shí)Tomcat產(chǎn)生警告菠赚,可以忽略。 生產(chǎn)環(huán)境建議不要 redploy,直接關(guān)閉/或Kill相關(guān)的JVM郑藏,然后從頭開(kāi)始啟動(dòng)即可衡查。

下面的情形更常見(jiàn),在重新部署web應(yīng)用時(shí), 很可能會(huì)引起?java.lang.OutOfMemoryError: Permgen space?錯(cuò)誤. 按道理說(shuō), redeploy 時(shí), Tomcat之類(lèi)的容器會(huì)使用新的 classloader 來(lái)加載新的 class, 讓垃圾收集器?將之前的 classloader (連同加載的class一起)清理掉,。

但實(shí)際情況可能并不樂(lè)觀, 很多第三方庫(kù), 以及某些受限的共享資源, 如 thread, JDBC驅(qū)動(dòng), 以及文件系統(tǒng)句柄(handles), 都會(huì)導(dǎo)致不能徹底卸載之前的 classloader. 那么在 redeploy 時(shí), 之前的class仍然駐留在PermGen中,?每次重新部署都會(huì)產(chǎn)生幾十MB必盖,甚至上百M(fèi)B的垃圾拌牲。

假設(shè)某個(gè)應(yīng)用在啟動(dòng)時(shí), 通過(guò)初始化代碼加載JDBC驅(qū)動(dòng)連接數(shù)據(jù)庫(kù). 根據(jù)JDBC規(guī)范, 驅(qū)動(dòng)會(huì)將自身注冊(cè)到?java.sql.DriverManager, 也就是將自身的一個(gè)實(shí)例(instance) 添加到?DriverManager?中的一個(gè) static 域。

那么, 當(dāng)應(yīng)用從容器中卸載時(shí),?java.sql.DriverManager?依然持有 JDBC實(shí)例(Tomcat經(jīng)常會(huì)發(fā)出警告), 而JDBC驅(qū)動(dòng)實(shí)例又持有?java.lang.Classloader?實(shí)例, 那么?垃圾收集器?也就沒(méi)辦法回收對(duì)應(yīng)的內(nèi)存空間歌粥。

而?java.lang.ClassLoader?實(shí)例持有著其加載的所有 class, 通常是幾十/上百 MB的內(nèi)存塌忽。可以看到, redeploy時(shí)會(huì)占用另一塊差不多大小的 PermGen 空間, 多次 redeploy 之后, 就會(huì)造成?java.lang.OutOfMemoryError: PermGen space?錯(cuò)誤, 在日志文件中, 你應(yīng)該會(huì)看到相關(guān)的錯(cuò)誤信息失驶。

解決方案

1.?解決程序啟動(dòng)時(shí)產(chǎn)生的 OutOfMemoryError

在程序啟動(dòng)時(shí), 如果 PermGen 耗盡而產(chǎn)生 OutOfMemoryError 錯(cuò)誤, 那很容易解決. 增加 PermGen 的大小, 讓程序擁有更多的內(nèi)存來(lái)加載 class 即可. 修改?-XX:MaxPermSize?啟動(dòng)參數(shù), 類(lèi)似下面這樣:

java -XX:MaxPermSize=512mcom.yourcompany.YourClass

1

2

以上配置允許JVM使用的最大 PermGen 空間為?512MB, 如果還不夠, 就會(huì)拋出?OutOfMemoryError土居。

2.?解決 redeploy 時(shí)產(chǎn)生的 OutOfMemoryError

我們可以進(jìn)行堆轉(zhuǎn)儲(chǔ)分析(heap dump analysis) —— 在 redeploy 之后, 執(zhí)行堆轉(zhuǎn)儲(chǔ), 類(lèi)似下面這樣:

jmap -dump:format=b,file=dump.hprof

1

2

然后通過(guò)堆轉(zhuǎn)儲(chǔ)分析器(如強(qiáng)悍的 Eclipse MAT)加載 dump 得到的文件。找出重復(fù)的類(lèi), 特別是類(lèi)加載器(classloader)對(duì)應(yīng)的 class. 你可能需要比對(duì)所有的 classloader, 來(lái)找出當(dāng)前正在使用的那個(gè)嬉探。

Eclipse MAT 在各個(gè)平臺(tái)都有獨(dú)立安裝包. 大約50MB左右, 官網(wǎng)下載地址:?http://www.eclipse.org/mat/downloads.php

對(duì)于不使用的類(lèi)加載器(inactive classloader), 需要先確定最短路徑的?GC root?, 看看是哪一個(gè)阻止其被?垃圾收集器?所回收. 這樣才能找到問(wèn)題的根源. 如果是第三方庫(kù)的原因, 那么可以搜索 Google/StackOverflow 來(lái)查找解決方案. 如果是自己的代碼問(wèn)題, 則需要在恰當(dāng)?shù)臅r(shí)機(jī)來(lái)解除相關(guān)引用擦耀。

3.?解決運(yùn)行時(shí)產(chǎn)生的 OutOfMemoryError

如果在運(yùn)行的過(guò)程中發(fā)生 OutOfMemoryError, 首先需要確認(rèn)?GC是否能從PermGen中卸載class。 官方的JVM在這方面是相當(dāng)?shù)谋J?在加載class之后,就一直讓其駐留在內(nèi)存中,即使這個(gè)類(lèi)不再被使用). 但是, 現(xiàn)代的應(yīng)用程序在運(yùn)行過(guò)程中, 會(huì)動(dòng)態(tài)創(chuàng)建大量的class, 而這些class的生命周期基本上都很短暫, 舊版本的JVM 不能很好地處理這些問(wèn)題涩堤。那么我們就需要允許JVM卸載class眷蜓。使用下面的啟動(dòng)參數(shù):

-XX:+CMSClassUnloadingEnabled

1

2

默認(rèn)情況下?CMSClassUnloadingEnabled?的值為false, 所以需要明確指定。 啟用以后,?GC 將會(huì)清理?PermGen, 卸載無(wú)用的 class. 當(dāng)然, 這個(gè)選項(xiàng)只有在設(shè)置?UseConcMarkSweepGC?時(shí)生效胎围。 如果使用了?ParallelGC, 或者?Serial GC?時(shí), 那么需要切換為CMS:

-XX:+UseConcMarkSweepGC

1

如果確定 class 可以被卸載, 假若還存在 OutOfMemoryError, 那就需要進(jìn)行堆轉(zhuǎn)儲(chǔ)分析了, 類(lèi)似下面這種命令:

jmap -dump:file=dump.hprof,format=b

1

2

然后通過(guò)堆轉(zhuǎn)儲(chǔ)分析器(如 Eclipse MAT) 加載 heap dump账磺。找出最重的 classloader, 也就是加載 class 數(shù)量最多的那個(gè). 通過(guò)加載的 class 及對(duì)應(yīng)的實(shí)例數(shù)量, 比對(duì)類(lèi)加載器, 找出最靠前的部分, 挨個(gè)進(jìn)行分析。

對(duì)于每個(gè)有嫌疑的類(lèi), 都需要手動(dòng)跟蹤到生成這些類(lèi)的代碼中, 以定位問(wèn)題痊远。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末垮抗,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碧聪,更是在濱河造成了極大的恐慌冒版,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逞姿,死亡現(xiàn)場(chǎng)離奇詭異辞嗡,居然都是意外死亡捆等,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)续室,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)栋烤,“玉大人,你說(shuō)我怎么就攤上這事挺狰∶鞴” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵丰泊,是天一觀的道長(zhǎng)薯定。 經(jīng)常有香客問(wèn)我,道長(zhǎng)瞳购,這世上最難降的妖魔是什么话侄? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮学赛,結(jié)果婚禮上年堆,老公的妹妹穿的比我還像新娘。我一直安慰自己盏浇,他們只是感情好变丧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著缠捌,像睡著了一般锄贷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上曼月,一...
    開(kāi)封第一講書(shū)人閱讀 51,208評(píng)論 1 299
  • 那天谊却,我揣著相機(jī)與錄音,去河邊找鬼哑芹。 笑死炎辨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的聪姿。 我是一名探鬼主播碴萧,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼末购!你這毒婦竟也來(lái)了破喻?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤盟榴,失蹤者是張志新(化名)和其女友劉穎曹质,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡羽德,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年几莽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宅静。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡章蚣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出姨夹,到底是詐尸還是另有隱情纤垂,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布匀伏,位于F島的核電站洒忧,受9級(jí)特大地震影響蝴韭,放射性物質(zhì)發(fā)生泄漏够颠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一榄鉴、第九天 我趴在偏房一處隱蔽的房頂上張望履磨。 院中可真熱鬧,春花似錦庆尘、人聲如沸剃诅。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至憔鬼,卻和暖如春蹲缠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鹿霸。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人翻屈。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像妻坝,于是被迫代替她去往敵國(guó)和親伸眶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 從三月份找實(shí)習(xí)到現(xiàn)在刽宪,面了一些公司厘贼,掛了不少,但最終還是拿到小米圣拄、百度嘴秸、阿里、京東、新浪赁遗、CVTE署辉、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,240評(píng)論 11 349
  • JVM是虛擬機(jī),也是一種規(guī)范岩四,他遵循著馮·諾依曼體系結(jié)構(gòu)的設(shè)計(jì)原理哭尝。馮·諾依曼體系結(jié)構(gòu)中,指出計(jì)算機(jī)處理的數(shù)據(jù)和指...
    Java架構(gòu)師Carl閱讀 3,392評(píng)論 0 108
  • -1- 保持同頻 之前家門(mén)口開(kāi)了一個(gè)吉他社剖煌,我下班比較晚的路上材鹦,有個(gè)young boy發(fā)傳單,剛開(kāi)始自動(dòng)屏蔽耕姊,可在...
    HanaZhuang閱讀 203評(píng)論 4 1
  • 人的觀點(diǎn)絕大多數(shù)是“隨波逐流”的桶唐,從來(lái)就沒(méi)有無(wú)緣無(wú)故的“堅(jiān)定”,我的一位朋友把“堅(jiān)定從容”掛在墻上茉兰,想方設(shè)法的提醒...
    liny52閱讀 139評(píng)論 0 0