某個(gè)大型項(xiàng)目(Use Case用例超過300個(gè)),在項(xiàng)目上線后,其Web應(yīng)用服務(wù)器經(jīng)常宕機(jī)。表現(xiàn)為:
1. 應(yīng)用服務(wù)器內(nèi)存長(zhǎng)期不合理占用,內(nèi)存經(jīng)常處于高位占用,很難回收到低位;
2. 應(yīng)用服務(wù)器極為不穩(wěn)定,幾乎每?jī)商熘匦聠?dòng)一次,有時(shí)甚至每天重新啟動(dòng)一次;
3. 應(yīng)用服務(wù)器經(jīng)常做Full GC(Garbage Collection),而且時(shí)間很長(zhǎng),大約需要30-40秒,應(yīng)用服務(wù)器在做Full GC的時(shí)候是不響應(yīng)客戶的交易請(qǐng)求的,非常影響系統(tǒng)性能。
一臺(tái)Unix服務(wù)器(4CPU,8G Memory)來部署本W(wǎng)eb應(yīng)用程序;Web應(yīng)用程序部署在中間件應(yīng)用服務(wù)器上;部署了一個(gè)節(jié)點(diǎn)(Node),只配置一個(gè)應(yīng)用服務(wù)器實(shí)例(Instance),沒有做Cluster部署。
Web應(yīng)用服務(wù)器啟動(dòng)腳本中的內(nèi)存參數(shù)
MEM_ARGS="-XX:MaxPermSize=128m -XX:MaxNewSize=512m -Xms3096m -Xmx3096m -XX:+Printetails -Xloggc:./inwebapp1/gc.$$" |
可以看出目前生產(chǎn)系統(tǒng)中Web應(yīng)用服務(wù)器的內(nèi)存分配為3G Memory。
Web應(yīng)用服務(wù)器的重要部署參數(shù)
參數(shù)名稱 | 參數(shù)值 | 參數(shù)解釋 |
kernel.default(Thread Count) | 120 | 執(zhí)行線程數(shù)目,是并發(fā)處理能力的重要參數(shù) |
Session Timeout | 240分鐘(4小時(shí)) | HttpSession會(huì)話超時(shí) |
內(nèi)存長(zhǎng)期占用并導(dǎo)致系統(tǒng)不穩(wěn)定一般有兩種可能:
1. 對(duì)象被大量創(chuàng)建而且被緩存,在舊的對(duì)象釋放前又有大量新的對(duì)象被創(chuàng)建使得內(nèi)存長(zhǎng)期高位占用。
- 表現(xiàn)為:內(nèi)存不斷被消耗、在高位時(shí)也很難回歸到低位,有大量的對(duì)象在不斷的創(chuàng)建,經(jīng)過很長(zhǎng)時(shí)間后又被回收。例如:在HttpSession中保存了大量的分頁查詢數(shù)據(jù),而HttpSession的會(huì)話超時(shí)時(shí)間設(shè)置過長(zhǎng)(例如:1天),那么在舊的對(duì)象釋放前又有大量新的對(duì)象在第二天產(chǎn)生。
- 解決辦法:對(duì)共享的對(duì)象可以采用池機(jī)制進(jìn)行緩存,避免各自創(chuàng)建;緩存的臨時(shí)對(duì)象應(yīng)該及時(shí)釋放;另一種辦法是擴(kuò)大系統(tǒng)的內(nèi)存容量。
2. 另一種情況就是內(nèi)存泄漏問題
- 表現(xiàn)為:內(nèi)存回收低位點(diǎn)不斷升高(以每次內(nèi)存回收的最低點(diǎn)連成一條直線,那么它是一條上升線);內(nèi)存回收的頻率也越來越高,內(nèi)存占用也越來越高,最終出現(xiàn)"Out of Memory Exception"的系統(tǒng)異常。
- 解決辦法:定位那些有內(nèi)存泄漏的類或?qū)ο蟛⑿薷耐晟七@些類以避免內(nèi)存泄漏。方法是:經(jīng)過一段時(shí)間的測(cè)試、監(jiān)控,如果某個(gè)類的對(duì)象數(shù)目屢創(chuàng)新高,即使在JVM Full GC后仍然數(shù)目降不下來,這些對(duì)象基本上是屬于內(nèi)存泄漏的對(duì)象了。
這里請(qǐng)看5月份 Web應(yīng)用服務(wù)器的內(nèi)存回收?qǐng)D形:
《注意:5月18日早上10點(diǎn)重新啟動(dòng)了Web服務(wù)器,5月20日早上又重新啟動(dòng)了Web服務(wù)器。》
- 在Web應(yīng)用重要部署參數(shù)中,我們知道:Session的超時(shí)時(shí)間為4個(gè)小時(shí),我們?cè)诒O(jiān)控平臺(tái)也觀測(cè)到:在18日晚上10點(diǎn)左右所有的會(huì)話都過期了,從圖形一中也能看出18日晚上確實(shí)系統(tǒng)的內(nèi)存有回收到40%(就象股票的高位跳水);
- 從圖形一(5月18日)中我們也能看到Full GC回收后的內(nèi)存占用率走勢(shì)(紅色曲線),上午基本平滑上升到20%(內(nèi)存占用率),中午開始上升到30%,下午上升到40%
- 從圖形二(5月19日)中我們也能看到Full GC回收后的內(nèi)存占用率走勢(shì)(紅色曲線),上午又上升到了60%,到下午上升到了70%。
- 從黃色曲線(GC花費(fèi)的時(shí)間,以秒為單位),F(xiàn)ull GC的頻率也在增快,時(shí)間耗費(fèi)也越來越長(zhǎng),在圖形一中基本高位在20秒左右,到19日基本都是30-40秒之間了。
通過上述分析,我們基本定位到了Web應(yīng)用服務(wù)器的內(nèi)存在高位長(zhǎng)期占用的原因了:是內(nèi)存泄露!并且正是由于這個(gè)原因?qū)е孪到y(tǒng)不穩(wěn)定、響應(yīng)客戶請(qǐng)求越來越慢的。
方法如下:
- 我們從圖形二中發(fā)現(xiàn),在8.95(將近9點(diǎn)鐘)到9.66(將近9點(diǎn)40)期間有幾次Full GC,但是有內(nèi)存泄漏,從占用率40%上升到50%左右,泄漏了大約10%的內(nèi)存,約300M;
- 我們?cè)谧约捍罱ǖ腤eb應(yīng)用服務(wù)器平臺(tái)(應(yīng)用軟件版本和生產(chǎn)版本一致)做這一階段相同的查詢交易;表明對(duì)同一個(gè)黑盒(Web應(yīng)用)施加同樣的刺激(相同的操作過程和查詢交易)以期重現(xiàn)現(xiàn)象;
- 我們使用Jprofiler工具對(duì)Web應(yīng)用服務(wù)器的內(nèi)存進(jìn)行實(shí)時(shí)監(jiān)控;
- 做完這些交易后,用戶退出系統(tǒng),并等待Web應(yīng)用服務(wù)器的HttpSession超時(shí)(我們這里設(shè)置為15分鐘);
- 我們對(duì)Web應(yīng)用服務(wù)器做了兩次強(qiáng)制性的內(nèi)存回收操作。
發(fā)現(xiàn)如下:
如圖三所示,內(nèi)存經(jīng)過HttpSession超時(shí)后,并強(qiáng)制gc后,仍然有大量的對(duì)象沒有釋放。例如:gov.gdlt.taxcore.comm.security.MenuNode,仍然有807個(gè)實(shí)例沒有釋放。
我們繼續(xù)追溯發(fā)現(xiàn),這些MenuNode首先存放在一個(gè)ArrayList對(duì)象中,然后發(fā)現(xiàn)這個(gè)ArrayList對(duì)象又是存放在WHsessionAttrVO對(duì)象的Map中,WHsessionAttrVO 對(duì)象又是存放在ExternalSessionManager的staic Map中(名稱為sessionMap),如圖四所示。
我們發(fā)現(xiàn)gov.gdlt.taxcore.taxevent.xtgl.comm.WHsessionAttrVO中保存了EJBSessionId信息(登錄用戶的唯一標(biāo)志,由用戶id+登錄時(shí)間戳組成,每天都不同)和一個(gè)HashMap,這個(gè)HashMap中的內(nèi)容有:
- ArrayList: 內(nèi)有MenuTreeNodes(菜單樹節(jié)點(diǎn))
- HashMap: 內(nèi)有操作人員代碼信息
- CurrentVersion:當(dāng)前版本號(hào)
- CurrentTime:當(dāng)前系統(tǒng)時(shí)間
WHsessionAttrVO這個(gè)對(duì)象的最終存放在ExternalSessionManager的static Map sessionMap中,由于ExternalSessionManager是一個(gè)全局的單實(shí)例,不會(huì)釋放,所以它的成員變量sessionMap中的數(shù)據(jù)也不會(huì)釋放,而Map中的Key值為EJBSessionId,每天登錄的用戶EJBSessionId都不同,就造成了每天的登錄信息(包括菜單信息)都保存在sessionMap中不會(huì)被釋放,最終造成了內(nèi)存的泄漏。
如上圖所示:WHsessionAttrsVO對(duì)象中除了有一個(gè)String對(duì)象(內(nèi)容是EJBSessionId),還有一個(gè)HashMap對(duì)象。
如上圖所示,這個(gè)HashMap中的內(nèi)容主要有menuTreeNodes為key,value為ArrayList的對(duì)象和以czrydminfo為key,value為HashMap對(duì)象的數(shù)據(jù)。
如上圖所示:menuTreeNodes為key,value為ArrayList對(duì)象中包含的對(duì)象有許多的MenuNode對(duì)象,封裝的都是用戶的菜單節(jié)點(diǎn)。
如上圖所示,最頂層(Root)的初始對(duì)象為一個(gè)ExternalSessionManager對(duì)象,其中的一個(gè)成員變量為static (靜態(tài)的),名稱為:sessionMap,這個(gè)對(duì)象是singleton方式的,全局只有一個(gè)。
我們從圖形一和圖形二中可以看出,每天應(yīng)用服務(wù)器損失大約40%的內(nèi)存,大約1G左右。
從圖形四可以看出,當(dāng)前用戶(Id=24400001129)有807個(gè)菜單項(xiàng)(每個(gè)菜單項(xiàng)為一個(gè)MenuNode 對(duì)象實(shí)例,圖形四中的這個(gè)實(shí)例的size為592 Byte),這些菜單數(shù)據(jù)和用戶基本登錄信息(czrydmInfo HashMap)也都存放在WHsessionAttrVO對(duì)象中,當(dāng)前這個(gè)WHsessionAttrVO對(duì)象的size為457K。
我們做如下估算:
假設(shè)平均每天有4千人(估計(jì)值,這個(gè)數(shù)值僅僅是5月19日峰值的1/2左右)登錄系統(tǒng)(有重復(fù)登錄的現(xiàn)象,例如:上午登錄一次,中午退出系統(tǒng),下午登錄一次),以平均每人占用200K(估計(jì)值,是用戶id=24400001129 的Size的1/2左右)來計(jì)算,一天泄漏的內(nèi)存約800M,比較符合目前內(nèi)存泄漏的情況。當(dāng)然,這種估計(jì)仍然需要經(jīng)過實(shí)踐的檢驗(yàn),方法是:當(dāng)這次發(fā)現(xiàn)的內(nèi)存泄漏問題解決后看系統(tǒng)是否還有其它內(nèi)存泄漏問題。
![]() |
ExternalSessionManager類是當(dāng)初某某軟件商設(shè)計(jì)的用來解決Web服務(wù)器負(fù)載均衡的模塊,這個(gè)類主要用來保存客戶的基本登錄信息(包括會(huì)話的EJBSessionId),以維護(hù)多個(gè)Web服務(wù)器之間的會(huì)話信息一致。
改進(jìn)方案有兩種:
-
從架構(gòu)設(shè)計(jì)方面改進(jìn)
實(shí)現(xiàn)Web層的負(fù)載均衡有很多標(biāo)準(zhǔn)的實(shí)現(xiàn)方式。例如:采用負(fù)載均衡設(shè)備(硬件或軟件)來實(shí)現(xiàn)。
如果采用新的Web層的負(fù)載均衡方式,那么就可以去掉ExternalSessionManager這個(gè)類了。
-
從應(yīng)用實(shí)現(xiàn)方面改進(jìn)
保留當(dāng)前的Web層的負(fù)載均衡設(shè)計(jì)機(jī)制,僅僅從應(yīng)用實(shí)現(xiàn)方面解決內(nèi)存泄漏問題,首先菜單信息不應(yīng)該保存在ExternalSessionManager中。其次,增加對(duì)ExternalSessionManager類中用戶會(huì)話登錄信息的清除,有幾種方式可以選擇:
- 被動(dòng)方式,當(dāng)HttpSession會(huì)話超時(shí)(或過期)被Web應(yīng)用服務(wù)器回收時(shí)清除相應(yīng)的ExternalSessionManager中的過期會(huì)話登錄信息。
- 主動(dòng)方式,可以采用任務(wù)定時(shí)清理每天的過期會(huì)話登錄信息或線程輪詢清理。
- 采用新的會(huì)話登錄信息存儲(chǔ)方式,ExternalSessionManager的sessionMap中的key值不再以EJBSessionId作為鍵值,而是以用戶id(EJBSessionId的前11位)代替。由于用戶id每天都是一樣的,所以不會(huì)造成內(nèi)存泄漏。保存得登錄信息也不再包含菜單節(jié)點(diǎn)信息,而只是登錄基本信息。最多也只是保存整個(gè)系統(tǒng)所有的用戶id及其基本登錄信息(大約每個(gè)用戶的登錄信息只有1.5K左右,而目前這個(gè)系統(tǒng)的營(yíng)業(yè)網(wǎng)點(diǎn)用戶為1萬左右,所以大約只占用Web服務(wù)器15M內(nèi)存)。
![]() |
采用的方案:某某軟件商采用了新的會(huì)話登錄信息存貯方案,即:ExternalSessionManager的成員變量sessionMap中不再保存用戶菜單信息,只保存基本的登錄信息;存儲(chǔ)方式采用用戶id(11位)作為鍵值(key)來保留用戶基本登錄信息。
基本分析:由于基本登錄信息只有1K左右,而目前內(nèi)網(wǎng)登錄的用戶總數(shù)也只有8887個(gè),所以只保存了大約10M-15M的信息在內(nèi)存,占用量很小,并且不會(huì)有內(nèi)存泄漏。用戶菜單信息保存在session中,如果用戶退出時(shí)點(diǎn)擊logout頁面,那么應(yīng)用服務(wù)器可以很快地釋放這部分內(nèi)存;如果用戶直接關(guān)閉窗口,那么保存在session中的菜單信息只有等會(huì)話超時(shí)后才會(huì)由系統(tǒng)清除并回收內(nèi)存。
監(jiān)控狀況:
如圖九所示,ExternalSessionManager中只保留了簡(jiǎn)單的登錄信息(Map中保存了WHsessionAttrVO對(duì)象),包括:當(dāng)前版本(currentversion),操作人員代碼基本信息(czrydmInfo),當(dāng)前時(shí)間(currenttime)。
如圖十所示,這個(gè)登錄用戶的基本信息只有1368 bytes,大約1.3K
如圖十一所示,一共有兩個(gè)用戶(相同的用戶id)登錄系統(tǒng),當(dāng)一個(gè)用戶使用logout頁面退出時(shí),保留在session中的菜單信息(MenuNode)立刻釋放了,所以Difference一欄減少了806個(gè)菜單項(xiàng)。
如圖十二所示,當(dāng)另外一個(gè)會(huì)話超時(shí)后,應(yīng)用服務(wù)器回收了整個(gè)會(huì)話的菜單信息(MenuNode),圖上已經(jīng)沒有MenuNode對(duì)象了。并且由于是同一個(gè)用戶登錄,所以保留在ExternalSessionManager成員變量sessionMap中的對(duì)象WHsessionAttrVO只有一個(gè)(id=24400001129),而沒有產(chǎn)生多個(gè),沒有因?yàn)槎啻蔚卿浂a(chǎn)生多個(gè)對(duì)象的后果,避免了內(nèi)存泄漏問題的出現(xiàn),解決了前期定位的內(nèi)存泄漏問題。
如圖十三所示,經(jīng)過gc內(nèi)存回收后,發(fā)現(xiàn)內(nèi)存回收比較穩(wěn)定,基本都回收到了最低點(diǎn),也證明了內(nèi)存沒有泄露。
結(jié)論與建議:從測(cè)試情況看,解決了前期定位的內(nèi)存泄漏問題。
生產(chǎn)系統(tǒng)實(shí)施后的監(jiān)控與分析
經(jīng)過調(diào)優(yōu)后,我們發(fā)現(xiàn):在2005年6月2日晚9點(diǎn)40左右重新部署、啟動(dòng)了Web應(yīng)用服務(wù)器(采用了新的調(diào)優(yōu)方案)。經(jīng)過幾天的監(jiān)控運(yùn)行,發(fā)現(xiàn)Web應(yīng)用服務(wù)器目前運(yùn)行基本穩(wěn)定,目前沒有出現(xiàn)新的內(nèi)存泄漏問題,下列圖示說明了這一點(diǎn)
如圖十四所示,6月2日晚21.7(21點(diǎn)42分)重新啟動(dòng)應(yīng)用服務(wù)器,內(nèi)存占用很少,大約為15%(請(qǐng)看紅色曲線),每次GC消耗的時(shí)間也很短,大約在5秒以內(nèi)(請(qǐng)看黃色曲線)。
如圖十五所示,在6月3日周五的整個(gè)工作日內(nèi),內(nèi)存的回收基本到位,回收位置控制在20%-30%之間,也就是在600M-900M之間(請(qǐng)看紅色曲線的最低點(diǎn)),始終可以回收2G的內(nèi)存供應(yīng)用程序使用,每次GC的時(shí)間最高不超過20秒,F(xiàn)ull GC平均在10秒左右,時(shí)間消耗比較短(請(qǐng)看黃色曲線)。
如圖十六所示,在周日休息日期間,Web應(yīng)用服務(wù)器全天只做了大約4次Full GC(黃色曲線中的小山峰),時(shí)間都在10秒以內(nèi);大的Full GC后,內(nèi)存只占用10%,內(nèi)存回收很徹底。
如圖十七所示,在周一工作日期間,內(nèi)存回收還是不錯(cuò)的,基本可以回收到30%(見紅色曲線的最低點(diǎn)),即:占用900M內(nèi)存空間,剩余2G的內(nèi)存空間;Full GC的時(shí)間大部分控制在20秒以內(nèi),平均15秒(見黃色曲線)。
如圖十八所示,在6月7日周二早上,大約8:30左右,Web應(yīng)用服務(wù)器作了一次Full GC,用了10秒的時(shí)間,把內(nèi)存回收到了10%的位置,為后續(xù)的使用騰出了90%的內(nèi)存空間。內(nèi)存回收仍然比較徹底,說明基本沒有內(nèi)存泄漏問題。
經(jīng)過這幾天的監(jiān)控分析,我們可以看出:
- Web應(yīng)用服務(wù)器的內(nèi)存使用已經(jīng)比較合理,內(nèi)存在工作日的占用在20%至30%之間,約1G的內(nèi)存占用,有2G的內(nèi)存空間富裕;而在空閑時(shí)間(周日,每天的凌晨等)內(nèi)存可以回收到10%,有90%的內(nèi)存空間富裕;
- Web應(yīng)用服務(wù)器的Full GC的次數(shù)明顯減少了并且每次Full GC占用的時(shí)間也很少,基本控制在10-20秒之間,有的甚至在10秒以內(nèi),明顯改善了內(nèi)網(wǎng)應(yīng)用服務(wù)器內(nèi)存的使用;
- 從6月2日重新部署之后,Web應(yīng)用服務(wù)器沒有出現(xiàn)宕機(jī)重啟的現(xiàn)象。
![]() |
通過本文,我們可以看到,內(nèi)存的泄露將會(huì)導(dǎo)致服務(wù)器的宕機(jī),系統(tǒng)性能就更別說了。對(duì)于系統(tǒng)內(nèi)存泄露問題應(yīng)該從服務(wù)器GC日志方面進(jìn)行早診斷,使用工具早確認(rèn)并提出解決方案,排除內(nèi)存泄露問題,提高系統(tǒng)性能,以規(guī)避項(xiàng)目風(fēng)險(xiǎn)。
本文轉(zhuǎn)自:http://yufeimen.javaeye.com/blog/70721
---------------------------------------------------------------------------------------------------------------------------------
說人之短,乃護(hù)己之短。夸己之長(zhǎng),乃忌人之長(zhǎng)。皆由存心不厚,識(shí)量太狹耳。能去此弊,可以進(jìn)德,可以遠(yuǎn)怨。
http://www.aygfsteel.com/szhswl
------------------------------------------------------------------------------------------------------ ----------------- ---------