內存佔用分析(Java Jprofile) ← J2EE企業級開發 ← 程序員之家論壇

在中間件應用服務器的整體調優中,有關於等待隊列、執行線程,EJB池以及數據庫連接池和Statement Cache方面的調優,這些都屬於系統參數方面的調優,本文主要從另外一個角度,也就是從應用的角度來解決中間件應用服務器的內存泄露問題,從這個角度來提高系統的穩定性和性能。 項目背景 問題描述 某個大型項目(Use Case用例超過300個),在項目上線後,其Web應用服務器經常宕機。表現爲: 1. 應用服務器內存長期不合理佔用,內存經常處於高位佔用,很難回收到低位; 2. 應用服務器極爲不穩定,幾乎每兩天重新啓動一次,有時甚至每天重新啓動一次; 3. 應用服務器經常做Full GC(Garbage Collection),而且時間很長,大約需要30-40秒,應用服務器在做Full GC的時候是不響應客戶的交易請求的,非常影響系統性能。 Web應用服務器的物理部署 一臺Unix服務器(4CPU,8G Memory)來部署本Web應用程序;Web應用程序部署在中間件應用服務器上;部署了一個節點(Node),只配置一個應用服務器實例(Instance),沒有做Cluster部署。 Web應用服務器啓動腳本中的內存參數
MEM_ARGS="-XX:MaxPermSize=128m -XX:MaxNewSize=512m -Xms3096m -Xmx3096m -XX:+Printetails -Xloggc:./inwebapp1/gc.$$"
可以看出目前生產系統中Web應用服務器的內存分配爲3G Memory。 Web應用服務器的重要部署參數
參數名稱 參數值 參數解釋
kernel.default(Thread Count) 120 執行線程數目,是併發處理能力的重要參數
Session Timeout 240分鐘(4小時) HttpSession會話超時
分析 分析方法 內存長期佔用並導致系統不穩定一般有兩種可能: 1. 對象被大量創建而且被緩存,在舊的對象釋放前又有大量新的對象被創建使得內存長期高位佔用。

  • 表現爲:內存不斷被消耗、在高位時也很難迴歸到低位,有大量的對象在不斷的創建,經過很長時間後又被回收。例如:在HttpSession中保存了大量的分頁查詢數據,而HttpSession的會話超時時間設置過長(例如:1天),那麼在舊的對象釋放前又有大量新的對象在第二天產生。
  • 解決辦法:對共享的對象可以採用池機制進行緩存,避免各自創建;緩存的臨時對象應該及時釋放;另一種辦法是擴大系統的內存容量。
2. 另一種情況就是內存泄漏問題

  • 表現爲:內存回收低位點不斷升高(以每次內存回收的最低點連成一條直線,那麼它是一條上升線);內存回收的頻率也越來越高,內存佔用也越來越高,最終出現"Out of Memory Exception"的系統異常。
  • 解決辦法:定位那些有內存泄漏的類或對象並修改完善這些類以避免內存泄漏。方法是:經過一段時間的測試、監控,如果某個類的對象數目屢創新高,即使在JVM Full GC後仍然數目降不下來,這些對象基本上是屬於內存泄漏的對象了。
問題定位 這裏請看5月份 Web應用服務器的內存回收圖形: 《注意:5月18日早上10點重新啓動了Web服務器,5月20日早上又重新啓動了Web服務器。》

  • 在Web應用重要部署參數中,我們知道:Session的超時時間爲4個小時,我們在監控平臺也觀測到:在18日晚上10點左右所有的會話都過期了,從圖形一中也能看出18日晚上確實系統的內存有回收到40%(就象股票的高位跳水);
  • 從圖形一(5月18日)中我們也能看到Full GC回收後的內存佔用率走勢(紅色曲線),上午基本平滑上升到20%(內存佔用率),中午開始上升到30%,下午上升到40%
  • 從圖形二(5月19日)中我們也能看到Full GC回收後的內存佔用率走勢(紅色曲線),上午又上升到了60%,到下午上升到了70%。
  • 從黃色曲線(GC花費的時間,以秒爲單位),Full GC的頻率也在增快,時間耗費也越來越長,在圖形一中基本高位在20秒左右,到19日基本都是30-40秒之間了。
圖形一 5月18日 image002.gif圖二 image004.gif 通過上述分析,我們基本定位到了Web應用服務器的內存在高位長期佔用的原因了:是內存泄露!並且正是由於這個原因導致系統不穩定、響應客戶請求越來越慢的。 解決方法 方法如下:

  • 我們從圖形二中發現,在8.95(將近9點鐘)到9.66(將近9點40)期間有幾次Full GC,但是有內存泄漏,從佔用率40%上升到50%左右,泄漏了大約10%的內存,約300M;
  • 我們在自己搭建的Web應用服務器平臺(應用軟件版本和生產版本一致)做這一階段相同的查詢交易;表明對同一個黑盒(Web應用)施加同樣的刺激(相同的操作過程和查詢交易)以期重現現象;
  • 我們使用Jprofiler工具對Web應用服務器的內存進行實時監控;
  • 做完這些交易後,用戶退出系統,並等待Web應用服務器的HttpSession超時(我們這裏設置爲15分鐘);
  • 我們對Web應用服務器做了兩次強制性的內存回收操作。
發現如下: 圖三 image007.gif 如圖三所示,內存經過HttpSession超時後,並強制gc後,仍然有大量的對象沒有釋放。例如:gov.gdlt.taxcore.comm.security.MenuNode,仍然有807個實例沒有釋放。 我們繼續追溯發現,這些MenuNode首先存放在一個ArrayList對象中,然後發現這個ArrayList對象又是存放在WHsessionAttrVO對象的Map中,WHsessionAttrVO 對象又是存放在ExternalSessionManager的staic Map中(名稱爲sessionMap),如圖四所示。 圖四 image009.gif 我們發現gov.gdlt.taxcore.taxevent.xtgl.comm.WHsessionAttrVO中保存了EJBSessionId信息(登錄用戶的唯一標誌,由用戶id+登錄時間戳組成,每天都不同)和一個HashMap,這個HashMap中的內容有:

  • ArrayList: 內有MenuTreeNodes(菜單樹節點)
  • HashMap: 內有操作人員代碼信息
  • CurrentVersion:當前版本號
  • CurrentTime:當前系統時間
WHsessionAttrVO這個對象的最終存放在ExternalSessionManager的static Map sessionMap中,由於ExternalSessionManager是一個全局的單實例,不會釋放,所以它的成員變量sessionMap中的數據也不會釋放,而Map中的Key值爲EJBSessionId,每天登錄的用戶EJBSessionId都不同,就造成了每天的登錄信息(包括菜單信息)都保存在sessionMap中不會被釋放,最終造成了內存的泄漏。 圖五 image011.jpg 如上圖所示:WHsessionAttrsVO對象中除了有一個String對象(內容是EJBSessionId),還有一個HashMap對象。 圖六 image013.gif 如上圖所示,這個HashMap中的內容主要有menuTreeNodes爲key,value爲ArrayList的對象和以czrydminfo爲key,value爲HashMap對象的數據。 圖七 image015.jpg 如上圖所示:menuTreeNodes爲key,value爲ArrayList對象中包含的對象有許多的MenuNode對象,封裝的都是用戶的菜單節點。 圖八 image017.jpg 如上圖所示,最頂層(Root)的初始對象爲一個ExternalSessionManager對象,其中的一個成員變量爲static (靜態的),名稱爲:sessionMap,這個對象是singleton方式的,全局只有一個。 初步估量 我們從圖形一和圖形二中可以看出,每天應用服務器損失大約40%的內存,大約1G左右。 從圖形四可以看出,當前用戶(Id=24400001129)有807個菜單項(每個菜單項爲一個MenuNode 對象實例,圖形四中的這個實例的size爲592 Byte),這些菜單數據和用戶基本登錄信息(czrydmInfo HashMap)也都存放在WHsessionAttrVO對象中,當前這個WHsessionAttrVO對象的size爲457K。 我們做如下估算: 假設平均每天有4千人(估計值,這個數值僅僅是5月19日峯值的1/2左右)登錄系統(有重複登錄的現象,例如:上午登錄一次,中午退出系統,下午登錄一次),以平均每人佔用200K(估計值,是用戶id=24400001129 的Size的1/2左右)來計算,一天泄漏的內存約800M,比較符合目前內存泄漏的情況。當然,這種估計仍然需要經過實踐的檢驗,方法是:當這次發現的內存泄漏問題解決後看系統是否還有其它內存泄漏問題。
blue_rule.gif
方案 ExternalSessionManager類是當初某某軟件商設計的用來解決Web服務器負載均衡的模塊,這個類主要用來保存客戶的基本登錄信息(包括會話的EJBSessionId),以維護多個Web服務器之間的會話信息一致。 改進方案有兩種:

  • 從架構設計方面改進 實現Web層的負載均衡有很多標準的實現方式。例如:採用負載均衡設備(硬件或軟件)來實現。 如果採用新的Web層的負載均衡方式,那麼就可以去掉ExternalSessionManager這個類了。
  • 從應用實現方面改進 保留當前的Web層的負載均衡設計機制,僅僅從應用實現方面解決內存泄漏問題,首先菜單信息不應該保存在ExternalSessionManager中。其次,增加對ExternalSessionManager類中用戶會話登錄信息的清除,有幾種方式可以選擇:

    • 被動方式,當HttpSession會話超時(或過期)被Web應用服務器回收時清除相應的ExternalSessionManager中的過期會話登錄信息。
    • 主動方式,可以採用任務定時清理每天的過期會話登錄信息或線程輪詢清理。
    • 採用新的會話登錄信息存儲方式,ExternalSessionManager的sessionMap中的key值不再以EJBSessionId作爲鍵值,而是以用戶id(EJBSessionId的前11位)代替。由於用戶id每天都是一樣的,所以不會造成內存泄漏。保存得登錄信息也不再包含菜單節點信息,而只是登錄基本信息。最多也只是保存整個系統所有的用戶id及其基本登錄信息(大約每個用戶的登錄信息只有1.5K左右,而目前這個系統的營業網點用戶爲1萬左右,所以大約只佔用Web服務器15M內存)。

blue_rule.gif
實施情況 採用的方案:某某軟件商採用了新的會話登錄信息存貯方案,即:ExternalSessionManager的成員變量sessionMap中不再保存用戶菜單信息,只保存基本的登錄信息;存儲方式採用用戶id(11位)作爲鍵值(key)來保留用戶基本登錄信息。 基本分析:由於基本登錄信息只有1K左右,而目前內網登錄的用戶總數也只有8887個,所以只保存了大約10M-15M的信息在內存,佔用量很小,並且不會有內存泄漏。用戶菜單信息保存在session中,如果用戶退出時點擊logout頁面,那麼應用服務器可以很快地釋放這部分內存;如果用戶直接關閉窗口,那麼保存在session中的菜單信息只有等會話超時後纔會由系統清除並回收內存。 監控狀況: 圖九 image019.jpg 如圖九所示,ExternalSessionManager中只保留了簡單的登錄信息(Map中保存了WHsessionAttrVO對象),包括:當前版本(currentversion),操作人員代碼基本信息(czrydmInfo),當前時間(currenttime)。 圖十 image021.jpg 如圖十所示,這個登錄用戶的基本信息只有1368 bytes,大約1.3K 圖十一 image023.jpg 如圖十一所示,一共有兩個用戶(相同的用戶id)登錄系統,當一個用戶使用logout頁面退出時,保留在session中的菜單信息(MenuNode)立刻釋放了,所以Difference一欄減少了806個菜單項。 圖十二 image025.jpg 如圖十二所示,當另外一個會話超時後,應用服務器回收了整個會話的菜單信息(MenuNode),圖上已經沒有MenuNode對象了。並且由於是同一個用戶登錄,所以保留在ExternalSessionManager成員變量sessionMap中的對象WHsessionAttrVO只有一個(id=24400001129),而沒有產生多個,沒有因爲多次登錄而產生多個對象的後果,避免了內存泄漏問題的出現,解決了前期定位的內存泄漏問題。 圖十三 image027.jpg 如圖十三所示,經過gc內存回收後,發現內存回收比較穩定,基本都回收到了最低點,也證明了內存沒有泄露。 結論與建議:從測試情況看,解決了前期定位的內存泄漏問題。 生產系統實施後的監控與分析 經過調優後,我們發現:在2005年6月2日晚9點40左右重新部署、啓動了Web應用服務器(採用了新的調優方案)。經過幾天的監控運行,發現Web應用服務器目前運行基本穩定,目前沒有出現新的內存泄漏問題,下列圖示說明了這一點 圖十四 2005年6月2日 image029.gif 如圖十四所示,6月2日晚21.7(21點42分)重新啓動應用服務器,內存佔用很少,大約爲15%(請看紅色曲線),每次GC消耗的時間也很短,大約在5秒以內(請看黃色曲線)。 圖十五 2005年6月3日週五 image031.gif 如圖十五所示,在6月3日週五的整個工作日內,內存的回收基本到位,回收位置控制在20%-30%之間,也就是在600M-900M之間(請看紅色曲線的最低點),始終可以回收2G的內存供應用程序使用,每次GC的時間最高不超過20秒,Full GC平均在10秒左右,時間消耗比較短(請看黃色曲線)。 圖十六2005年6月5日週日 image033.gif 如圖十六所示,在週日休息日期間,Web應用服務器全天只做了大約4次Full GC(黃色曲線中的小山峯),時間都在10秒以內;大的Full GC後,內存只佔用10%,內存回收很徹底。 圖十七 2005年6月6日週一 image035.gif 如圖十七所示,在週一工作日期間,內存回收還是不錯的,基本可以回收到30%(見紅色曲線的最低點),即:佔用900M內存空間,剩餘2G的內存空間;Full GC的時間大部分控制在20秒以內,平均15秒(見黃色曲線)。 圖十八 2005年6月7日週二 image037.gif 如圖十八所示,在6月7日週二早上,大約8:30左右,Web應用服務器作了一次Full GC,用了10秒的時間,把內存回收到了10%的位置,爲後續的使用騰出了90%的內存空間。內存回收仍然比較徹底,說明基本沒有內存泄漏問題。 經過這幾天的監控分析,我們可以看出:

  • Web應用服務器的內存使用已經比較合理,內存在工作日的佔用在20%至30%之間,約1G的內存佔用,有2G的內存空間富裕;而在空閒時間(週日,每天的凌晨等)內存可以回收到10%,有90%的內存空間富裕;
  • Web應用服務器的Full GC的次數明顯減少了並且每次Full GC佔用的時間也很少,基本控制在10-20秒之間,有的甚至在10秒以內,明顯改善了內網應用服務器內存的使用;
  • 從6月2日重新部署之後,Web應用服務器沒有出現宕機重啓的現象。

blue_rule.gif
總結 通過本文,我們可以看到,內存的泄露將會導致服務器的宕機,系統性能就更別說了。對於系統內存泄露問題應該從服務器GC日誌方面進行早診斷,使用工具早確認並提出解決方案,排除內存泄露問題,提高系統性能,以規避項目風險。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章