Groovy靈活,但請小心--記一次FullGC分析

? ? ? ?某天下午正在噼里啪啦的寫代碼時继谚,釘釘群瘋狂的發(fā)FullGC告警豪诲,登陸相關(guān)機(jī)器榆芦,jps -lv | grep 找到PID后,執(zhí)行 jstat -gccause pid 2000 pid顯示如下:

CG的原因是per space已滿墙懂,再執(zhí)行jmap -heap pid,輸出如下:

很明顯,Per space已經(jīng)用完了扮念,當(dāng)前使用的jdk版本為:Java(TM) SE Runtime Environment (build 1.6.0_29-b11)JVM參數(shù)如下:

永久代最大內(nèi)存為96M损搬,這個區(qū)域存儲了class字節(jié)碼以及一些常量信息,要溢出除非是以下幾種情況:

1柜与、該區(qū)域設(shè)置過小巧勤,根本無法裝載應(yīng)用的所需的所有class

2、應(yīng)用里大量動態(tài)生成class,如頻繁編譯jsp或大量使用動態(tài)代理生成許多proxy

3弄匕、應(yīng)用自定義classloader颅悉,頻繁load class

4、應(yīng)用大量生成字符串迁匠,并調(diào)用string.intern()

出問題的服務(wù)一個class平均大小在10k左右剩瓶,96M可以加載9800多個class,JVM6在初始化是會加載2000左右類城丧,應(yīng)用本身只有99個class[linux遞歸統(tǒng)計文件數(shù):ls -lR | grep "^-" |wc -l]延曙,加上引用的類在4000個左右,再加上一些字符串常量亡哄,大約在60M左右枝缔,因此不可能是第一個原因,該服務(wù)是基于Spring MVC的純后臺服務(wù)蚊惯,只有一個jsp管理頁面愿卸,不管是Spring的AOP還是其他一些動態(tài)代理拐辽,都是在程序啟動就生成好了的,因此也基本可排除第二個原因擦酌,剩下3俱诸, 4,在應(yīng)用的代碼層面赊舶,沒有顯示調(diào)string.intern()睁搭,也沒有顯示自定義classloader,但無法排除引用的代碼里是否有笼平,jmap 打印的信息看园骆,per區(qū)的確是占用99%,究竟是什么數(shù)據(jù)消耗內(nèi)存呢寓调,jvm既然有命令可以看各個區(qū)的消耗锌唾,應(yīng)該有命令可以查看永久帶的信息,jmap --help夺英,果然找到一個參數(shù) :

-permstat to print permanent generation statistics

執(zhí)行該命令晌涕,首先打印的是字符串占用的空間,20658 intern Strings occupying 2174792bytes痛悯,接下輸出滿屏的groovy/lang/GroovyClassLoader,該GroovyClassLoader只有3個instance余黎,卻有4800多條紀(jì)錄,也就是說這些相同的classloader在不斷l(xiāng)oad class到虛擬機(jī)载萌,直到per區(qū)內(nèi)存消耗完惧财。直覺告訴我,肯定是某個代碼static引用了GroovyClassLoader扭仁,并不斷觸發(fā)該類load class垮衷,問題點(diǎn)已經(jīng)找到,為了不影響線上業(yè)務(wù)乖坠,先重啟服務(wù)搀突,接下來去代碼里搜關(guān)鍵字:groovy,沒有結(jié)果瓤帚,正準(zhǔn)備通過maven打印依賴關(guān)系【mvndependency:tree】查找線索時描姚,突然想到有個工具servlet里使用groovy動態(tài)執(zhí)行一些java代碼,groovy是將腳本動態(tài)編譯后load到虛擬機(jī)執(zhí)行的戈次,自然會產(chǎn)生許多class轩勘,問題應(yīng)該就是這里了,接下來我們梳理下整個事情來龍去脈:

1 代碼里創(chuàng)建了一個static GroovyShell 怯邪,GroovyShell持有GroovyClassLoader

2 每次傳一些java代碼過來绊寻,調(diào)用GroovyShell.eval(xxx)執(zhí)行

3 ?GroovyShell動態(tài)編譯腳本,再調(diào)用GroovyClassLoader load到虛擬機(jī)執(zhí)行。

4 當(dāng)腳本執(zhí)行完澄步,此前動態(tài)生成class就沒有什么用了冰蘑,但字節(jié)碼還駐留在per區(qū),并且不會被卸載村缸,隨著eval調(diào)用次數(shù)增多祠肥, Per區(qū)內(nèi)存就一點(diǎn)點(diǎn)的被消耗完。

為什么這些無用的字節(jié)碼沒被JVM回收呢梯皿,這得從class卸載機(jī)制說起仇箱,JVM的Per區(qū)沒有單獨(dú)的Garbage collector,這個區(qū)域只是在老年代FullGC時順帶回收的东羹,一個class只有在滿足以下條件時剂桥,才能被JVM 卸載

1. 該類所有的實(shí)例已經(jīng)被回收

2. 該類的ClassLoder已經(jīng)被回收

3. 該類對應(yīng)的Java.lang.Class對象沒有任何對方被引用

我們的代碼中,因?yàn)镚roovyClassLoader被GroovyShell引用属提,而GroovyShell被應(yīng)用代碼static引用权逗,整個應(yīng)用運(yùn)行期間,該引用鏈一直都在冤议,無法滿足條件2斟薇,所以即使我們腳本已經(jīng)執(zhí)行完,但動態(tài)生成的字節(jié)碼還會一直駐留JVM直到內(nèi)存溢出求类。因?yàn)閖ava并沒有提供卸載class的接口奔垦,所以我們只能想辦法滿足上述3個條件,讓JVM在必要時卸載class尸疆,解決思路就是要打破上述引用鏈,方案如下:

1 將GroovyShell由static改為局部變量

2 將GroovyShell放到WeakReference里惶岭,既能避免重復(fù)創(chuàng)建寿弱,又支持JVM卸載class

由于問題代碼只是一個運(yùn)維型工具類,時間上不是很敏感按灶,直接采用第一種方案症革。在本地測試,以下代碼執(zhí)行循環(huán)到6000多次時Per區(qū)內(nèi)存溢出鸯旁,用Jconsole觀察噪矛,加載的類直線上升,JVM加:XX:+TraceClassLoading ?-XX:+TraceClassUnloading铺罢,控制臺只打印load lcass艇挨,不見unload ?class日志,信息如下:

而改成局部變量后韭赘,控制臺出現(xiàn)unload class日志缩滨,程序一直運(yùn)行直到完成,再無per區(qū)溢出。

許多運(yùn)行在JVM上的腳本語言(如Groovy)為我們帶來很多便利脉漏,如在不重啟服務(wù)的情況下苞冯,修改服務(wù)的運(yùn)行數(shù)據(jù),清空緩存等侧巨,但若使用不當(dāng)舅锄,則會帶來致命打擊,所以使用時請小心謹(jǐn)慎司忱。在java項目中巧娱,不管是從工程的可維護(hù)性還是運(yùn)行性能來講,都不建議大量使用腳本烘贴。

Note:Java8去掉Per gen禁添,改為metaspace,并且把stirng常量也挪到heap里桨踪,metaspace采用直接內(nèi)存老翘,會自動調(diào)節(jié)大小,該區(qū)域大小只受限于物理內(nèi)存锻离,因此不會再有PerGen溢出問題铺峭,

擴(kuò)展閱讀:jmap命令詳解? ?JVM老年代??jstat命令??GC cause參數(shù)解析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市汽纠,隨后出現(xiàn)的幾起案子卫键,更是在濱河造成了極大的恐慌,老刑警劉巖虱朵,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莉炉,死亡現(xiàn)場離奇詭異,居然都是意外死亡碴犬,警方通過查閱死者的電腦和手機(jī)絮宁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來服协,“玉大人绍昂,你說我怎么就攤上這事〕ズ桑” “怎么了窘游?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長跳纳。 經(jīng)常有香客問我忍饰,道長,這世上最難降的妖魔是什么棒旗? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任喘批,我火速辦了婚禮撩荣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘饶深。我一直安慰自己餐曹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布敌厘。 她就那樣靜靜地躺著台猴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪俱两。 梳的紋絲不亂的頭發(fā)上饱狂,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機(jī)與錄音宪彩,去河邊找鬼休讳。 笑死,一個胖子當(dāng)著我的面吹牛尿孔,可吹牛的內(nèi)容都是我干的俊柔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼活合,長吁一口氣:“原來是場噩夢啊……” “哼雏婶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起白指,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤留晚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后告嘲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體错维,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年状蜗,在試婚紗的時候發(fā)現(xiàn)自己被綠了需五。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡轧坎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泽示,到底是詐尸還是另有隱情缸血,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布械筛,位于F島的核電站捎泻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏埋哟。R本人自食惡果不足惜笆豁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一郎汪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧闯狱,春花似錦煞赢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瘦陈,卻和暖如春凝危,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背晨逝。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工蛾默, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人捉貌。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓支鸡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親昏翰。 傳聞我的和親對象是個殘疾皇子苍匆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)棚菊,斷路器浸踩,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • 一 、java虛擬機(jī)底層結(jié)構(gòu)詳解 我們知道统求,一個JVM實(shí)例的行為不光是它自己的事检碗,還涉及到它的子系統(tǒng)、存儲區(qū)域码邻、...
    葡萄喃喃囈語閱讀 1,476評論 0 4
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,510評論 25 707
  • 如果說家是一座房子折剃,那孝就是地基,沒有孝像屋,家庭就會不安怕犁,家庭就會心散。家庭要祥和己莺,“百善孝為先”奏甫,“孝”是做人之本...
    尹長海閱讀 277評論 0 1
  • 4月19日消息,據(jù)《衛(wèi)報》報道凌受,談到機(jī)器人的未來阵子,遞歸神經(jīng)網(wǎng)絡(luò)之父、德國計算機(jī)科學(xué)家尤爾根·施米德胡貝(Jürge...
    愛過米閱讀 197評論 0 0