讓人頭疼的WAS內存溢出,看銀行運維人員如何優雅的解決
1 引言
WAS(IBM WebSphere Application Server)是IBM發布的一款成熟的企業級Web中間件產品,憑借其可靠性與穩定性,一直是國內大型商業銀行Web服務的主流選擇??稍俜€定也會出問題,在日常的生產運維中,WAS應用問題的排查確實讓筆者這種銀行運維人員頭疼。一方面廠商提供技術支持的時效性與準確性有待改善,另一方面像IBM其他產品一樣,網上開放的可參考和借鑒的資料太少,發生WAS問題時著實讓人無從下手。不過不要緊,魯迅先生曾經說過,“走的人多了,自然就有路了”,筆者作為具有多年WAS運維經驗的老鳥,下面就把自己在應對WAS內存溢出方面的知識總結一下,為大家介紹一下如何優雅的應對WAS內存溢出。
2 IBM JAVA內存管理
要應對WAS內存溢出,必須對IBM對JAVA內存的管理有所了解,下面,筆者就簡單介紹一下IBM是如何管理JAVA內存的。不同于大家經常使用的Oracle Java,WAS使用的JAVA是內置于WAS內部的IBM JAVA,與Oracle Java在JVM、配置參數等方面有著顯著不同。
IBM JAVA 同樣包含JDK、JRE、JVM三層,其關系如圖所示:
圖1 JDK、JRE、JVM關系
圖2 JVM 運行時內存區域
程序計數器區域
Java虛擬機支持多線程運行,所以對于每個線程,都需要一個指示其運行程序位置的指針,這個指針指向當前程序運行方法的地址。
Java虛擬機棧
每一個Java線程都擁有一個私有的Java虛擬機棧。像其他傳統語言一樣,Java虛擬機棧保存了程序調用時的局部變量和部分結果(稱之為Frame)。
方法區、運行時常量池
方法區存放運行時常量池、字段以及方法(包括構造方法、特殊方法)代碼。在IBM Java 8版本中,所有加載的類都存放在稱之為Metaspace的空間中,Metaspace使用操作系統本地內存空間。
本地方法區域
為了支持操作系統本地方法(如C語言)調用,虛擬機中在本地方法區域中存儲本地方法調用的棧信息。
堆空間
堆是JVM運行時內存中最大的區域,也是和程序開發密切相關區域,所有的對象實例(包括基本類型)、數組都存放在這個區域。和傳統的C、C++語言不同,Java語言不需要開發人員顯式地進行內存的申請和釋放,而是由JVM的Allocator(內存分配器)和Garbage Collection(內存垃圾回收器,簡稱GC)負責管理內存。我們最常見的內存溢出“java.lang.OutOfMemoryError : Java heap space”也主要和該區域有關。下面我們將著重闡述IBM J9 VM堆空間相關模型和垃圾回收策略。
堆空間內存結構和垃圾回收策略(GC)
J9 VM支持多種不同的GC策略,不同的GC策略對應不同的Heap內存模型及分配回收算法,不同的GC策略適應于不同的業務場景,對于大多數系統(特別是交易類系統)來說,可使用“Generational Concurrent Garbage Collector”策略(簡稱gencon,參數:-Xgcpolicy:gencon可以指定使用該策略),這也是J9 VM的默認GC策略,本文主要詳細介紹該策略。
“Generational Concurrent Garbage Collector”策略特別適合存在非常多短生命周期對象的應用,即對象申請完之后,很快就不被使用,可以被GC回收。而一般的交易類系統,都符合這種場景。
在該策略下,Heap內存被劃分成新區域(Nursery)、老區域(Tenured)。所有對象創建后都被分配到Nursery區域,之后如果該對象一直標記為可用,則會被自動到Tenured區域。
圖3 J9 VM 默認堆空間內存模型
圖4 Local GC過程
上文提到,在一般場景下,大部分對象創建后,很快就不被使用、存活的對象較少,所以Local GC移動的數據也很少,而且Local GC后,可以得到很大的Allocate空間,這樣就減小了GC時間。在JVM中,GC意味著所有運行中線程都要停下來(Pause)等待GC結束,GC完成后,才可以繼續運行,所以Local GC可以減少因GC帶來的系統吞吐量下降的影響。
發生堆空間分配失敗或者調用System.gc()方法后,觸發Global GC過程。Global GC通過標記、清除、壓縮過程來盡可能釋放JVM內存空間。Global GC需要獲得整個JVM的排他控制權,所以當進行Global GC時,所有應用線程也將暫停。當Global GC結束后,應用線程將恢復執行。
3 常見的WAS內存溢出原因
上面我們介紹了IBM Java內存管理的模型和策略。理解上述模型后,我們可以清楚的知道為何會發生內存溢出:
(1)JVM內部或者JVM間接使用的操作系統內存分配失敗后觸發內存溢出報錯。JVM內存區域中,除了程序計數器區域外,Java虛擬機棧、堆空間、方法區、運行時常量池、本地方法棧都可能會發生內存溢出報錯。
(2)對于堆空間,當堆空間已經盡可能擴展,并且JVM花費了95%以上的時間在GC時,也會觸發內存溢出報錯。
以上兩點是內存溢出的基本要點,但實際生產系統由于運行環境往往較為復雜,在處理實際問題時,我們還應結合環境配置和業務場景來分析。通過總結實際運維過程中經驗,可以將內存溢出原因分為如下幾類:
(1)堆內存大小上限配置過低
由于Java程序所能使用的堆空間上限完全取決于JVM啟動時的參數配置,當堆空間上限參數設置過低,即使操作系統物理內存空閑較多,應用程序也無法使用。所以在問題排查時,我們首先應該明確系統配置的堆空間上限(由Xmx參數指定),一般不能使用堆大小上限默認值。
(2)程序內存泄漏導致內存持續增長
如果程序存在內存泄漏,即使已經不再使用的內存仍將無法被GC回收釋放,JVM內存將持續增長(而且,由于內存使用率逐漸升高,將會更加頻繁的觸發GC,反復GC又會引發CPU過高),最終導致堆內存空間滿而引發內存溢出。
(3)數據查詢交易返回記錄數過多或者程序申請使用大內存對象
當程序過度地使用內存大對象或數組,導致無法申請足夠的內存空間而引發內存溢出。例如,在實際生產中,可能存在應用程序讀取整表數據或情況(數據條數在幾萬條以上),極易引發內存溢出。
(4)物理內存過低或因其他進程消耗過多內存引發內存溢出
即使我們設定了合理的JVM內存空間大小上限,但也有可能因為本地操作系統本身可用內存過低、無法實現內存空間的動態擴充,進而導致內存溢出;也可能因為在同一個操作系統上運行的其他JVM或者本地進程使用過多的內存導致內存溢出;由于JVM的部分區域(如Metaspace、DirectMemory等)直接使用的是操作系統內存,所以當操作系統內存過低,但創建本地線程過多、加載類過多時也有可能發生內存溢出異常;當程序過度使用DirectMemory也會引發內存溢出。
(5)交易量突然增大
如果我們將JVM堆內存上限設為M,每支交易處理需要使用的堆內存是N,那么當同時處理的交易量X突然增多N*X>M時,就容易觸發內存溢出。
4 如何優雅的應對WAS內存溢出
當發生內存溢出后,首先要做的是恢復生產,恢復因內存溢出而宕機的Server?;謴蜕a后,可按照下面步驟進行內存溢出原因分析。
收集環境信息
內存溢出分析首先要做的就是收集環境信息和日志信息。
收集日志文件
表 1 收集日志文件表
分析應用日志
查看SystemOut.log日志java.lang.OutOfMemoryError的提示信息,確定內存溢出發生在JVM的哪個區域之后,查看SystemOut.log、SystemErr.log中應用交易日志,分析是否可疑的異常交易。
分析堆內存使用趨勢
一般內存分析,第一步先查看JVM內存使用情況,即通過“IBM Pattern Modeling and Analysis Tool for Java Garbage Collector”工具,打開native_stderr.log文件,查看JVM堆空間內存使用曲線:
對于大對象或數組使用導致內存溢出的曲線一般如下圖所示,存在曲線突然升高的情況:
圖5 大對象內存溢出堆空間趨勢圖
內存泄漏導致內存溢出的曲線一般如下圖所示,曲線緩慢上升(紅色曲線):
圖6 內存泄漏程序堆空間趨勢圖
找到堆空間可疑內存溢出點
分析線程現場信息
使用“IBM Thread and Monitor Dump Analyzer for Java”工具,分析javacore文件。檢查內存溢出時正在執行的交易、正在執行的方法。
非堆空間內存溢出
如果出現“java.lang.OutOfMemoryError: 本機內存耗盡”內存溢出報錯,則需要考慮DirectByteBuffer內存區域引發內存溢出。
5 如何在具體場景應用
圖7:發生問題時某臺WAS服務器的內存監控情況
第三步,首先我們來查看日志文件,下面分別是SystemOut.log和SystemErr.log的部分內容。果然,在問題時點附近的錯誤日志中看到了OutOfMemoryError,同時在應用日志中看到了一些正在執行的sql,那么到底是哪個程序在作怪,又是為什么產生了內存溢出呢。
圖8:問題時點的應用日志
圖9:問題時點的錯誤日志
第四步,看來僅從日志是無法定位具體問題的,筆者接下來要運用工具來解決問題了。筆者先后用IBM HeapAnalyzer和IBM Thread and Monitor Dump Analyzer for Java工具,分別對Heapdump文件及Javacore文件進行了具體的分析。對Heapdump文件的解析結果顯示,某個List居然存在68萬多個對象,占用了近50%的內存空間。對Javacore文件的分析結果顯示,發生溢出時某支交易線程一直處于等待狀態。
圖10:Heapdump文件的分析結果
圖11:Heapdump文件的分析結果
6 如何預防或解決內存溢出問題
7 最后