JVM性能問(wèn)題的處理依據(jù):運(yùn)行日志與異常堆棧、GC日志(printGCDetails)涝影、線(xiàn)程快照(threaddump/javacore)痹届、堆轉(zhuǎn)儲(chǔ)快照(heapdump)蹬蚁。
一、工具
1. JDK命令行工具
1)jps:process status外臂,輸出進(jìn)程ID坐儿,main class;提供多個(gè)參數(shù):-v輸出虛擬機(jī)啟動(dòng)參數(shù)宋光;-l輸出主類(lèi)的全名/jar包的地址貌矿;-m輸出main函數(shù)接收的參數(shù)
2)jinfo:用于查看、調(diào)整VM配置罪佳。jps -v只查看顯式指定的VM參數(shù)逛漫,但jinfo查看所有參數(shù),并修改允許運(yùn)行時(shí)修改的參數(shù)赘艳。jinfo pid酌毡;jinfo -flag <flagName> <newFlagValue>。
3)jstat:顯示VM運(yùn)行數(shù)據(jù)statistics蕾管。服務(wù)器上定位VM性能問(wèn)題的首選阔馋,但可視化不及VisualVM。jstat-<option> vmid interval count娇掏,如jstat-gc 2764 250 20表示每250ms查看一次2764進(jìn)程的GC情況呕寝,共20次。提供option包括1)class:監(jiān)測(cè)類(lèi)加載、卸載下梢;2)gc:監(jiān)測(cè)Eden客蹋、Survivor、老年代孽江、永久代的容量讶坯、已用空間、GC時(shí)間岗屏;3)gccause:上次GC原因辆琅;4)compile:輸出JIT編譯過(guò)的方法,耗時(shí)这刷。
4)jmap:提供option包括:1)dump:生成heapdump婉烟;2)finalizerinfo:查詢(xún)finalise執(zhí)行隊(duì)列;3)heap:顯示堆信息暇屋。
5)jhat:heap analysis tool似袁,分析heapdump并用html展示結(jié)果。分析工作耗時(shí)耗CPU咐刨,通酬夹疲拷貝到其他機(jī)器用visual VM等工具分析,而不是在運(yùn)行應(yīng)用服務(wù)器用jhat分析定鸟。相對(duì)于Visual VM而涉,jhat功能簡(jiǎn)陋,Intellij沒(méi)有捆綁的分析工具联予。
6)jstack:線(xiàn)程椨て祝快照,分析線(xiàn)程長(zhǎng)時(shí)間停頓原因躯泰,如死鎖/死循環(huán)/請(qǐng)求外部資源等待谭羔。參數(shù):-F無(wú)響應(yīng)時(shí)強(qiáng)制輸出線(xiàn)程棧;-l鎖的附加信息麦向;-m本地方法棧瘟裸;Thread類(lèi)的getAllStackTrace可實(shí)現(xiàn)管理員頁(yè)面,通過(guò)瀏覽器查看線(xiàn)程堆棧诵竭。
2. JDK的可視化工具
1)Jconsole:運(yùn)行jdk/bin下面的jconsole.exe啟動(dòng)Jconsole话告,自動(dòng)搜索虛擬機(jī)進(jìn)程,雙擊某進(jìn)程進(jìn)行監(jiān)控卵慰,支持監(jiān)控遠(yuǎn)程虛擬機(jī)沙郭;展示堆使用情況,線(xiàn)程數(shù)目等裳朋。
2)VisualVM(主流):基于插件可擴(kuò)展病线,顯示進(jìn)程/進(jìn)程配置信息,生成和瀏覽heapdump。性能分析profiler:運(yùn)行時(shí)分析方法級(jí)的CPU執(zhí)行時(shí)間和內(nèi)存送挑,對(duì)程序運(yùn)行效率影響大绑莺,通常不用于生產(chǎn)環(huán)境;CPU分析統(tǒng)計(jì)每個(gè)方法的執(zhí)行次數(shù)/耗時(shí)惕耕;內(nèi)存分析統(tǒng)計(jì)每個(gè)方法關(guān)聯(lián)的對(duì)象數(shù)和對(duì)象所占空間纺裁。BTrace:在不停止目標(biāo)程序運(yùn)行的前提下,動(dòng)態(tài)加入原本不存在的調(diào)試代碼
二司澎、調(diào)優(yōu)案例實(shí)戰(zhàn)與分析
1. 高性能硬件上的服務(wù)部署策略??
問(wèn)題:在線(xiàn)文檔網(wǎng)站欺缘,原有硬件1.5G堆內(nèi)存,用戶(hù)感覺(jué)使用緩慢挤安;進(jìn)行了硬件升級(jí)16G內(nèi)存谚殊,12G堆內(nèi)存,反而明顯卡頓漱受;系統(tǒng)監(jiān)控顯示經(jīng)常出現(xiàn)長(zhǎng)時(shí)間失去響應(yīng)的情況。
原因:使用吞吐量?jī)?yōu)先的收集器骡送,頻繁發(fā)生Full GC昂羡,每次耗時(shí)14秒;頻繁Full GC的原因:文檔反序列化生成的大對(duì)象未被minor GC回收摔踱,直接進(jìn)老年代導(dǎo)致老年代很快滿(mǎn)載
解決方式:1. 通過(guò)64位JDK使用大內(nèi)存虐先,用戶(hù)交互型(時(shí)間敏感)應(yīng)用必須控制full GC次數(shù)(如每天一次),深夜定時(shí)觸發(fā)full GC派敷∮寂控制full GC次數(shù)的關(guān)鍵是多數(shù)對(duì)象(尤其大對(duì)象)符合朝生夕死,以保障老年代的穩(wěn)定篮愉。網(wǎng)站應(yīng)用中對(duì)象的生命周期多是請(qǐng)求/頁(yè)面級(jí)腐芍,代碼合理情況下能保證Full GC的頻率。當(dāng)前64位JDK的問(wèn)題在于:性能低于32位试躏,堆溢出無(wú)法生成dump文件猪勇,或dump文件過(guò)大難以分析。2. 若干32位虛擬機(jī)建立邏輯集群颠蕴,在物理機(jī)啟動(dòng)多個(gè)應(yīng)用進(jìn)程泣刹,分配不同端口,通過(guò)負(fù)載均衡器分配訪(fǎng)問(wèn)請(qǐng)求犀被;問(wèn)題:容易出現(xiàn)資源競(jìng)爭(zhēng)椅您,比如磁盤(pán)資源競(jìng)爭(zhēng)引起的IO異常。
最終解決方案:調(diào)整為4個(gè)32位JDK的邏輯集群寡键;使用CMS收集器
2. 集群間同步導(dǎo)致的內(nèi)存溢出
問(wèn)題:MIS系統(tǒng)掀泳;硬件是兩臺(tái)小型機(jī),每個(gè)機(jī)器部署3個(gè)實(shí)例,共享數(shù)據(jù)存儲(chǔ)在數(shù)據(jù)庫(kù)和全局緩存开伏。全局緩存啟用后膀跌,服務(wù)正常使用了一段時(shí)間,最近不定期出現(xiàn)內(nèi)存溢出固灵。
排查原因:監(jiān)測(cè)GC捅伤,發(fā)現(xiàn)內(nèi)存不溢出時(shí)GC運(yùn)行合理:每次GC后內(nèi)存都回到合理水平;沒(méi)有代碼更新巫玻,排除代碼引起的內(nèi)存泄漏丛忆。讓?xiě)?yīng)用帶HeapDumpOnOutOfMemoryError參數(shù)執(zhí)行,管理員拿到最近內(nèi)存溢出后的heapdump文件仍秤,發(fā)現(xiàn)大量NAKACK對(duì)象熄诡,該對(duì)象服務(wù)于全局緩存。問(wèn)題一部分在于JBossCache本身的缺陷(論壇上有討論诗力,并且后續(xù)有版本更新)凰浮,另一部分在于MIS系統(tǒng)實(shí)現(xiàn)方式的缺陷(使用全局緩存時(shí)可以有頻繁的讀操作,但不適合有頻繁的寫(xiě)操作)
3. 堆外內(nèi)存導(dǎo)致的溢出??
問(wèn)題&排查:基于B/S架構(gòu)的電子考試系統(tǒng)苇本;測(cè)試發(fā)現(xiàn)服務(wù)器不定時(shí)拋出OOM異常袜茧,管理員把內(nèi)存調(diào)大后OOM異常更頻繁;加入HeapDumpOnOutOfMemory參數(shù)瓣窄,沒(méi)有輸出笛厦;運(yùn)行jstat,堆/方法區(qū)內(nèi)存壓力不大俺夕。日志中錯(cuò)誤堆棧:OOM-null裳凸,直接內(nèi)存溢出。
原因:除了堆內(nèi)存劝贸,直接內(nèi)存/棧內(nèi)存/socket緩存區(qū)/JNI代碼/虛擬機(jī)和GC也需要占用內(nèi)存姨谷,本例中硬件2G內(nèi)存,堆內(nèi)存1.6G映九,那堆外內(nèi)存<0.4G菠秒;不同于新生代/老年代,直接內(nèi)存不足時(shí)不會(huì)通知收集器進(jìn)行GC氯迂,只能等待Full GC發(fā)生時(shí)順帶清理直接內(nèi)存践叠,否則內(nèi)存溢出并在catch語(yǔ)句中調(diào)用system.gc觸發(fā)full GC,如果虛擬機(jī)打開(kāi)了DisableExplicitGC嚼蚀,gg禁灼。
Socket緩存區(qū):每個(gè)socket連接都需要receive和send兩個(gè)緩存區(qū),連接數(shù)過(guò)多也會(huì)OOM:Too many open files轿曙;JNI用于調(diào)用本地庫(kù)弄捕;虛擬機(jī)和GC執(zhí)行占用內(nèi)存僻孝。
4. 外部命令導(dǎo)致系統(tǒng)緩慢
問(wèn)題:數(shù)字校園系統(tǒng),壓力測(cè)試發(fā)現(xiàn)響應(yīng)慢守谓,監(jiān)控發(fā)現(xiàn)CPU占用率高穿铆,且占用CPU多數(shù)資源的并非應(yīng)用本身(應(yīng)用的CPU利用率應(yīng)占主要地位)。
排查:通過(guò)Dtrace發(fā)現(xiàn)fork大量占用CPU斋荞,linux用fork產(chǎn)生新進(jìn)程荞雏。檢查代碼大量調(diào)用Runtime.getRuntime().exec()執(zhí)行外部shell腳本獲取系統(tǒng)信息,它會(huì)克隆一個(gè)和當(dāng)前虛擬機(jī)擁有相同環(huán)境變量的進(jìn)程平酿,用于執(zhí)行外部命令凤优,最后退出進(jìn)程;頻繁調(diào)用產(chǎn)生的創(chuàng)建進(jìn)程的開(kāi)銷(xiāo)大蜈彼。
5. 服務(wù)器JVM進(jìn)程崩潰
問(wèn)題:MIS系統(tǒng)筑辨,硬件是2個(gè)HP系統(tǒng)(2個(gè)CPU,8G內(nèi)存)幸逆,正常運(yùn)行一段時(shí)間后棍辕,頻繁出現(xiàn)虛擬機(jī)進(jìn)程自動(dòng)關(guān)閉。
排查:崩潰前錯(cuò)誤日志包含大量connection reset遠(yuǎn)程斷開(kāi)鏈接異常还绘。該系統(tǒng)最近與另一個(gè)系統(tǒng)A集成楚昭,通過(guò)soapui調(diào)用系統(tǒng)A,發(fā)現(xiàn)要3分鐘才能響應(yīng)蚕甥,且經(jīng)常出現(xiàn)斷開(kāi)連接哪替。MIS系統(tǒng)雖然采用異步方式調(diào)用系統(tǒng)A栋荸,但速度完全不對(duì)等導(dǎo)致等待的線(xiàn)程和socket連接越來(lái)越多菇怀,最終導(dǎo)致虛擬機(jī)崩潰。
解決:通知A系統(tǒng)修復(fù)無(wú)法使用的接口晌块;異步調(diào)用改為生產(chǎn)者/消費(fèi)者模式的消息隊(duì)列爱沟。
6. 不恰當(dāng)數(shù)據(jù)結(jié)構(gòu)導(dǎo)致內(nèi)存占用過(guò)大
問(wèn)題:后臺(tái)服務(wù)器,采用ParNew+CMS收集器匆背,平時(shí)minorGC只需要30ms呼伸,但業(yè)務(wù)上需要每10分鐘加載一個(gè)80M的文件進(jìn)行處理,在內(nèi)存中形成100萬(wàn)個(gè)HashMap entry钝尸,這段時(shí)間的Minor GC會(huì)造成0.5s停頓括享。因?yàn)榧虞d文件時(shí)新生代迅速填滿(mǎn)從而引發(fā)Minor GC,同時(shí)對(duì)象仍然存活引發(fā)大量拷貝珍促。
解決方案:1.不修改程序铃辖,僅從GC調(diào)優(yōu)的角度解決:去掉survivor,新生代的存活對(duì)象直接進(jìn)入老年代猪叙。2.治本的方案是修改代碼娇斩,不用hashMap存儲(chǔ)數(shù)據(jù)仁卷。hashmap存儲(chǔ)數(shù)據(jù)空間效率低,HashMap中key和value是有效數(shù)據(jù)犬第,兩個(gè)長(zhǎng)整型共16字節(jié); 長(zhǎng)整型包裝為L(zhǎng)ong需要疊加8B的Markword锦积,8B的Klass指針,膨脹為24字節(jié)歉嗓。兩個(gè)Long組成hashmap entry還需要16B的對(duì)象頭丰介,8B的next字段,4B的hash字段遥椿,和為了對(duì)齊的4B填充基矮,以及hashMap對(duì)entry的8B引用,實(shí)際消耗內(nèi)存88B冠场〖医剑空間利用率16/88
三、Eclipse啟動(dòng)速度調(diào)優(yōu)
啟動(dòng)時(shí)間包含:GC4.2秒碴裙,full GC19次钢悲,共3.1s;minorGC 378次舔株,共1.1s莺琳;類(lèi)加載:9115個(gè),4.1s载慈;JIT編譯時(shí)間:2s惭等,由后臺(tái)線(xiàn)程完成。
如何優(yōu)化:
1)虛擬機(jī)版本升級(jí)办铡,希望從虛擬機(jī)本身得到免費(fèi)的性能提升辞做。升級(jí)之后意外出現(xiàn)OOM,打開(kāi)visual VM寡具,查看堆曲線(xiàn)和永久代曲線(xiàn)秤茅。堆曲線(xiàn)正常:堆大小的曲線(xiàn)和使用的堆的曲線(xiàn)一直存在很大的間隔,每當(dāng)兩條線(xiàn)接近時(shí)童叠,最大堆的曲線(xiàn)會(huì)快速向上轉(zhuǎn)向(堆擴(kuò)容)框喳,而使用的堆曲線(xiàn)則向下(觸發(fā)了一次GC)。永久代曲線(xiàn)不正常:永久代大小曲線(xiàn)和使用曲線(xiàn)幾乎重合厦坛,說(shuō)明永久代沒(méi)有可回收的資源五垮,也沒(méi)有可擴(kuò)展空間,因此內(nèi)存溢出是永久代引起的杜秸。通過(guò)配置MaxPermSize來(lái)解決放仗,默認(rèn)值64M。
2)調(diào)整內(nèi)存設(shè)置控制GC頻率:Minor GC頻繁亩歹,新生代空間太小匙监,通過(guò)-Xmn設(shè)置凡橱。Full GC執(zhí)行效率:回收時(shí)只做了擴(kuò)容,回收效率低亭姥,固定堆內(nèi)存和永久代大小稼钩,避免內(nèi)存自動(dòng)擴(kuò)展。Eclipse用戶(hù)通常選擇run in background达罗,CMS適合后臺(tái)運(yùn)行坝撑,在eclipse.ini文件中加入jvm配置參數(shù)。