在中間件應(yīng)用服務(wù)器的整體調(diào)優(yōu)中,有關(guān)于等待隊列、執(zhí)行線程,EJB池以及數(shù)據(jù)庫連接池和Statement Cache方面的調(diào)優(yōu),這些都屬于系統(tǒng)參數(shù)方面的調(diào)優(yōu),本文主要從另外一個角度,也就是從應(yīng)用的角度來解決中間件應(yīng)用服務(wù)器的內(nèi)存泄露問題,從這個角度來提高系統(tǒng)的穩(wěn)定性和性能。
項目背景
問題描述
某個大型項目(Use Case用例超過300個),在項目上線后,其Web應(yīng)用服務(wù)器經(jīng)常宕機。表現(xiàn)為:
1. 應(yīng)用服務(wù)器內(nèi)存長期不合理占用,內(nèi)存經(jīng)常處于高位占用,很難回收到低位;
2. 應(yīng)用服務(wù)器極為不穩(wěn)定,幾乎每兩天重新啟動一次,有時甚至每天重新啟動一次;
3. 應(yīng)用服務(wù)器經(jīng)常做Full GC(Garbage Collection),而且時間很長,大約需要30-40秒,應(yīng)用服務(wù)器在做Full GC的時候是不響應(yīng)客戶的交易請求的,非常影響系統(tǒng)性能。
Web應(yīng)用服務(wù)器的物理部署
一臺Unix服務(wù)器(4CPU,8G Memory)來部署本W(wǎng)eb應(yīng)用程序;Web應(yīng)用程序部署在中間件應(yīng)用服務(wù)器上;部署了一個節(jié)點(Node),只配置一個應(yīng)用服務(wù)器實例(Instance),沒有做Cluster部署。
Web應(yīng)用服務(wù)器啟動腳本中的內(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小時) |
HttpSession會話超時 |
分析
分析方法
內(nèi)存長期占用并導(dǎo)致系統(tǒng)不穩(wěn)定一般有兩種可能:
1. 對象被大量創(chuàng)建而且被緩存,在舊的對象釋放前又有大量新的對象被創(chuàng)建使得內(nèi)存長期高位占用。
- 表現(xiàn)為:內(nèi)存不斷被消耗、在高位時也很難回歸到低位,有大量的對象在不斷的創(chuàng)建,經(jīng)過很長時間后又被回收。例如:在HttpSession中保存了大量的分頁查詢數(shù)據(jù),而HttpSession的會話超時時間設(shè)置過長(例如:1天),那么在舊的對象釋放前又有大量新的對象在第二天產(chǎn)生。
- 解決辦法:對共享的對象可以采用池機制進行緩存,避免各自創(chuàng)建;緩存的臨時對象應(yīng)該及時釋放;另一種辦法是擴大系統(tǒng)的內(nèi)存容量。
2. 另一種情況就是內(nèi)存泄漏問題
- 表現(xiàn)為:內(nèi)存回收低位點不斷升高(以每次內(nèi)存回收的最低點連成一條直線,那么它是一條上升線);內(nèi)存回收的頻率也越來越高,內(nèi)存占用也越來越高,最終出現(xiàn)"Out of Memory Exception"的系統(tǒng)異常。
- 解決辦法:定位那些有內(nèi)存泄漏的類或?qū)ο蟛⑿薷耐晟七@些類以避免內(nèi)存泄漏。方法是:經(jīng)過一段時間的測試、監(jiān)控,如果某個類的對象數(shù)目屢創(chuàng)新高,即使在JVM Full GC后仍然數(shù)目降不下來,這些對象基本上是屬于內(nèi)存泄漏的對象了。
問題定位
這里請看5月份 Web應(yīng)用服務(wù)器的內(nèi)存回收圖形:
《注意:5月18日早上10點重新啟動了Web服務(wù)器,5月20日早上又重新啟動了Web服務(wù)器?!?
- 在Web應(yīng)用重要部署參數(shù)中,我們知道:Session的超時時間為4個小時,我們在監(jiān)控平臺也觀測到:在18日晚上10點左右所有的會話都過期了,從圖形一中也能看出18日晚上確實系統(tǒng)的內(nèi)存有回收到40%(就象股票的高位跳水);
- 從圖形一(5月18日)中我們也能看到Full GC回收后的內(nèi)存占用率走勢(紅色曲線),上午基本平滑上升到20%(內(nèi)存占用率),中午開始上升到30%,下午上升到40%
- 從圖形二(5月19日)中我們也能看到Full GC回收后的內(nèi)存占用率走勢(紅色曲線),上午又上升到了60%,到下午上升到了70%。
- 從黃色曲線(GC花費的時間,以秒為單位),F(xiàn)ull GC的頻率也在增快,時間耗費也越來越長,在圖形一中基本高位在20秒左右,到19日基本都是30-40秒之間了。
圖形一 5月18日

圖二

通過上述分析,我們基本定位到了Web應(yīng)用服務(wù)器的內(nèi)存在高位長期占用的原因了:是內(nèi)存泄露!并且正是由于這個原因?qū)е孪到y(tǒng)不穩(wěn)定、響應(yīng)客戶請求越來越慢的。
解決方法
方法如下:
- 我們從圖形二中發(fā)現(xiàn),在8.95(將近9點鐘)到9.66(將近9點40)期間有幾次Full GC,但是有內(nèi)存泄漏,從占用率40%上升到50%左右,泄漏了大約10%的內(nèi)存,約300M;
- 我們在自己搭建的Web應(yīng)用服務(wù)器平臺(應(yīng)用軟件版本和生產(chǎn)版本一致)做這一階段相同的查詢交易;表明對同一個黑盒(Web應(yīng)用)施加同樣的刺激(相同的操作過程和查詢交易)以期重現(xiàn)現(xiàn)象;
- 我們使用Jprofiler工具對Web應(yīng)用服務(wù)器的內(nèi)存進行實時監(jiān)控;
- 做完這些交易后,用戶退出系統(tǒng),并等待Web應(yīng)用服務(wù)器的HttpSession超時(我們這里設(shè)置為15分鐘);
- 我們對Web應(yīng)用服務(wù)器做了兩次強制性的內(nèi)存回收操作。
發(fā)現(xiàn)如下:
圖三

如圖三所示,內(nèi)存經(jīng)過HttpSession超時后,并強制gc后,仍然有大量的對象沒有釋放。例如:gov.gdlt.taxcore.comm.security.MenuNode,仍然有807個實例沒有釋放。
我們繼續(xù)追溯發(fā)現(xiàn),這些MenuNode首先存放在一個ArrayList對象中,然后發(fā)現(xiàn)這個ArrayList對象又是存放在WHsessionAttrVO對象的Map中,WHsessionAttrVO 對象又是存放在ExternalSessionManager的staic Map中(名稱為sessionMap),如圖四所示。
圖四

我們發(fā)現(xiàn)gov.gdlt.taxcore.taxevent.xtgl.comm.WHsessionAttrVO中保存了EJBSessionId信息(登錄用戶的唯一標志,由用戶id+登錄時間戳組成,每天都不同)和一個HashMap,這個HashMap中的內(nèi)容有:
- ArrayList: 內(nèi)有MenuTreeNodes(菜單樹節(jié)點)
- HashMap: 內(nèi)有操作人員代碼信息
- CurrentVersion:當前版本號
- CurrentTime:當前系統(tǒng)時間
WHsessionAttrVO這個對象的最終存放在ExternalSessionManager的static Map sessionMap中,由于ExternalSessionManager是一個全局的單實例,不會釋放,所以它的成員變量sessionMap中的數(shù)據(jù)也不會釋放,而Map中的Key值為EJBSessionId,每天登錄的用戶EJBSessionId都不同,就造成了每天的登錄信息(包括菜單信息)都保存在sessionMap中不會被釋放,最終造成了內(nèi)存的泄漏。
圖五

如上圖所示:WHsessionAttrsVO對象中除了有一個String對象(內(nèi)容是EJBSessionId),還有一個HashMap對象。
圖六

如上圖所示,這個HashMap中的內(nèi)容主要有menuTreeNodes為key,value為ArrayList的對象和以czrydminfo為key,value為HashMap對象的數(shù)據(jù)。
圖七

如上圖所示:menuTreeNodes為key,value為ArrayList對象中包含的對象有許多的MenuNode對象,封裝的都是用戶的菜單節(jié)點。
圖八

如上圖所示,最頂層(Root)的初始對象為一個ExternalSessionManager對象,其中的一個成員變量為static (靜態(tài)的),名稱為:sessionMap,這個對象是singleton方式的,全局只有一個。
初步估量
我們從圖形一和圖形二中可以看出,每天應(yīng)用服務(wù)器損失大約40%的內(nèi)存,大約1G左右。
從圖形四可以看出,當前用戶(Id=24400001129)有807個菜單項(每個菜單項為一個MenuNode 對象實例,圖形四中的這個實例的size為592 Byte),這些菜單數(shù)據(jù)和用戶基本登錄信息(czrydmInfo HashMap)也都存放在WHsessionAttrVO對象中,當前這個WHsessionAttrVO對象的size為457K。
我們做如下估算:
假設(shè)平均每天有4千人(估計值,這個數(shù)值僅僅是5月19日峰值的1/2左右)登錄系統(tǒng)(有重復(fù)登錄的現(xiàn)象,例如:上午登錄一次,中午退出系統(tǒng),下午登錄一次),以平均每人占用200K(估計值,是用戶id=24400001129 的Size的1/2左右)來計算,一天泄漏的內(nèi)存約800M,比較符合目前內(nèi)存泄漏的情況。當然,這種估計仍然需要經(jīng)過實踐的檢驗,方法是:當這次發(fā)現(xiàn)的內(nèi)存泄漏問題解決后看系統(tǒng)是否還有其它內(nèi)存泄漏問題。
方案
ExternalSessionManager類是當初某某軟件商設(shè)計的用來解決Web服務(wù)器負載均衡的模塊,這個類主要用來保存客戶的基本登錄信息(包括會話的EJBSessionId),以維護多個Web服務(wù)器之間的會話信息一致。
改進方案有兩種:
-
從架構(gòu)設(shè)計方面改進
實現(xiàn)Web層的負載均衡有很多標準的實現(xiàn)方式。例如:采用負載均衡設(shè)備(硬件或軟件)來實現(xiàn)。
如果采用新的Web層的負載均衡方式,那么就可以去掉ExternalSessionManager這個類了。
-
從應(yīng)用實現(xiàn)方面改進
保留當前的Web層的負載均衡設(shè)計機制,僅僅從應(yīng)用實現(xiàn)方面解決內(nèi)存泄漏問題,首先菜單信息不應(yīng)該保存在ExternalSessionManager中。其次,增加對ExternalSessionManager類中用戶會話登錄信息的清除,有幾種方式可以選擇:
- 被動方式,當HttpSession會話超時(或過期)被Web應(yīng)用服務(wù)器回收時清除相應(yīng)的ExternalSessionManager中的過期會話登錄信息。
- 主動方式,可以采用任務(wù)定時清理每天的過期會話登錄信息或線程輪詢清理。
- 采用新的會話登錄信息存儲方式,ExternalSessionManager的sessionMap中的key值不再以EJBSessionId作為鍵值,而是以用戶id(EJBSessionId的前11位)代替。由于用戶id每天都是一樣的,所以不會造成內(nèi)存泄漏。保存得登錄信息也不再包含菜單節(jié)點信息,而只是登錄基本信息。最多也只是保存整個系統(tǒng)所有的用戶id及其基本登錄信息(大約每個用戶的登錄信息只有1.5K左右,而目前這個系統(tǒng)的營業(yè)網(wǎng)點用戶為1萬左右,所以大約只占用Web服務(wù)器15M內(nèi)存)。
實施情況
采用的方案:某某軟件商采用了新的會話登錄信息存貯方案,即:ExternalSessionManager的成員變量sessionMap中不再保存用戶菜單信息,只保存基本的登錄信息;存儲方式采用用戶id(11位)作為鍵值(key)來保留用戶基本登錄信息。
基本分析:由于基本登錄信息只有1K左右,而目前內(nèi)網(wǎng)登錄的用戶總數(shù)也只有8887個,所以只保存了大約10M-15M的信息在內(nèi)存,占用量很小,并且不會有內(nèi)存泄漏。用戶菜單信息保存在session中,如果用戶退出時點擊logout頁面,那么應(yīng)用服務(wù)器可以很快地釋放這部分內(nèi)存;如果用戶直接關(guān)閉窗口,那么保存在session中的菜單信息只有等會話超時后才會由系統(tǒng)清除并回收內(nèi)存。
監(jiān)控狀況:
圖九

如圖九所示,ExternalSessionManager中只保留了簡單的登錄信息(Map中保存了WHsessionAttrVO對象),包括:當前版本(currentversion),操作人員代碼基本信息(czrydmInfo),當前時間(currenttime)。
圖十

如圖十所示,這個登錄用戶的基本信息只有1368 bytes,大約1.3K
圖十一

如圖十一所示,一共有兩個用戶(相同的用戶id)登錄系統(tǒng),當一個用戶使用logout頁面退出時,保留在session中的菜單信息(MenuNode)立刻釋放了,所以Difference一欄減少了806個菜單項。
圖十二

如圖十二所示,當另外一個會話超時后,應(yīng)用服務(wù)器回收了整個會話的菜單信息(MenuNode),圖上已經(jīng)沒有MenuNode對象了。并且由于是同一個用戶登錄,所以保留在ExternalSessionManager成員變量sessionMap中的對象WHsessionAttrVO只有一個(id=24400001129),而沒有產(chǎn)生多個,沒有因為多次登錄而產(chǎn)生多個對象的后果,避免了內(nèi)存泄漏問題的出現(xiàn),解決了前期定位的內(nèi)存泄漏問題。
圖十三

如圖十三所示,經(jīng)過gc內(nèi)存回收后,發(fā)現(xiàn)內(nèi)存回收比較穩(wěn)定,基本都回收到了最低點,也證明了內(nèi)存沒有泄露。
結(jié)論與建議:從測試情況看,解決了前期定位的內(nèi)存泄漏問題。
生產(chǎn)系統(tǒng)實施后的監(jiān)控與分析
經(jīng)過調(diào)優(yōu)后,我們發(fā)現(xiàn):在2005年6月2日晚9點40左右重新部署、啟動了Web應(yīng)用服務(wù)器(采用了新的調(diào)優(yōu)方案)。經(jīng)過幾天的監(jiān)控運行,發(fā)現(xiàn)Web應(yīng)用服務(wù)器目前運行基本穩(wěn)定,目前沒有出現(xiàn)新的內(nèi)存泄漏問題,下列圖示說明了這一點
圖十四 2005年6月2日

如圖十四所示,6月2日晚21.7(21點42分)重新啟動應(yīng)用服務(wù)器,內(nèi)存占用很少,大約為15%(請看紅色曲線),每次GC消耗的時間也很短,大約在5秒以內(nèi)(請看黃色曲線)。
圖十五 2005年6月3日周五

如圖十五所示,在6月3日周五的整個工作日內(nèi),內(nèi)存的回收基本到位,回收位置控制在20%-30%之間,也就是在600M-900M之間(請看紅色曲線的最低點),始終可以回收2G的內(nèi)存供應(yīng)用程序使用,每次GC的時間最高不超過20秒,F(xiàn)ull GC平均在10秒左右,時間消耗比較短(請看黃色曲線)。
圖十六2005年6月5日周日

如圖十六所示,在周日休息日期間,Web應(yīng)用服務(wù)器全天只做了大約4次Full GC(黃色曲線中的小山峰),時間都在10秒以內(nèi);大的Full GC后,內(nèi)存只占用10%,內(nèi)存回收很徹底。
圖十七 2005年6月6日周一

如圖十七所示,在周一工作日期間,內(nèi)存回收還是不錯的,基本可以回收到30%(見紅色曲線的最低點),即:占用900M內(nèi)存空間,剩余2G的內(nèi)存空間;Full GC的時間大部分控制在20秒以內(nèi),平均15秒(見黃色曲線)。
圖十八 2005年6月7日周二

如圖十八所示,在6月7日周二早上,大約8:30左右,Web應(yīng)用服務(wù)器作了一次Full GC,用了10秒的時間,把內(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)存空間富裕;而在空閑時間(周日,每天的凌晨等)內(nèi)存可以回收到10%,有90%的內(nèi)存空間富裕;
- Web應(yīng)用服務(wù)器的Full GC的次數(shù)明顯減少了并且每次Full GC占用的時間也很少,基本控制在10-20秒之間,有的甚至在10秒以內(nèi),明顯改善了內(nèi)網(wǎng)應(yīng)用服務(wù)器內(nèi)存的使用;
- 從6月2日重新部署之后,Web應(yīng)用服務(wù)器沒有出現(xiàn)宕機重啟的現(xiàn)象。
總結(jié)
通過本文,我們可以看到,內(nèi)存的泄露將會導(dǎo)致服務(wù)器的宕機,系統(tǒng)性能就更別說了。對于系統(tǒng)內(nèi)存泄露問題應(yīng)該從服務(wù)器GC日志方面進行早診斷,使用工具早確認并提出解決方案,排除內(nèi)存泄露問題,提高系統(tǒng)性能,以規(guī)避項目風(fēng)險。