IBM WebSphere Application Server診斷和調優(二)

作者:佚名  2007-06-25
IT168 技術文檔】近段時間,我們項目中用到的WebSphere應用服務器(WAS),但在客戶的production環境下極不穩定,經常宕機。給客戶造成非 常不好的影響,同時,也給項目組很大壓力。為此,我們花了近一個月時間對其診斷,現在基本上穩定了,需要繼續觀察一段時間。現在我主要將工作做一個階段性 的總結。
我們的產品環境是:WAS6.0+DB2 8.1+AIX5.3+RS/6000。在該產品環境下,出現的問題非常多,現象如下:
WAS經常不穩定、宕機幾乎一天一次,經常報告OutOfMemory(內存泄漏嗎?NO)。
DB2連接數過大,有時把DB2撐死,有時也把AIX撐死。
AIX虛擬內存報錯、分頁報錯、IO也報錯、還有很多其它莫名奇妙的錯。

    總是,每次問題發生的現象和理論上的總是不一致,導致我們不知道從何入手,也無從檢測自己的優化參數。咨詢過多次IBM技術支持,只解決了某些局部問題。
雖然問題依然存在,但我想,解決問題的思路、特別是理論基礎,還是有一些規律和原則。

對于WAS這塊,我近段時間的主要時間集中在以下幾個方面(時間順序):
1、Java性能監測工具:Jprofiler,也用到Jprobe。后來發現Jprofiler在AIX下幾乎不可用。
2、IBM Java虛擬機和WAS技術細節,特別是IBM JVM的GC原理,我發現它和sun、bea的差別很大。
3、IBM的heap分析器Heap Analyzer、GCCollector。這兩個事后監測工具非常實用,特別是我們的產品運行環境,非測試環境。
4、某些Application的懷疑和診斷。
5、AIX診斷,我幾乎沒有這個能力,只能常規監測一下,需另請高人。

我打算將本文分成以下幾個部分總結:
JVM原理、IBM JVM的GC策略和調優。
Jprofiler和IBM工具的實際體會
WAS的診斷體會和AIX調優

    下面開始主題吧,可能比較零碎,另外,開始的理論篇基本上看書都可以,我只是總結一下,再添加一些自己的理解。

以下是我參考的最重要的兩本電子書和一些網站:
《Inside Java Vrtual Machine》:半部分有約四章我認為非常棒,其它章節可能意義不大。
《The Java Virtual Machine Specification, 2nd》:前半部分有兩三章很不錯,不過可以對照上一本書看。
sun的hotspot虛擬機技術:
http://java.sun.com/javase/technologies/hotspot/
BEA的JRockit虛擬機技術:http://edocs.bea.com/jrockit/geninfo/genintro/index.html JVM技術文檔入口,虛擬機理論,內存泄漏診斷等的索引頁。
IBM診斷資料:
http://www-128.ibm.com/developerworks/java/jdk/diagnosis/ 上面有一個500多頁的pdf文檔,對IBM JVM技術和診斷講解很深入。

    我不得不提的是,在查資料這塊,BEA和Sun都有很好的官方文檔和論壇支持,并且官方文檔導航非常好。雖然IBM的診斷資料也不少,但需要搜索,其搜索 是很痛苦的。而且,IBM官方論壇很差。如果用IBM的產品出問題,切記:找IBM技術支持,千萬不要蒙著頭搞!反正它們的產品很少免費。說實話,它們的 技術支持還是挺負責的,一般會為你推薦很多support資料,而該資料往往都在developerworks網站上,屬于support那個頻道,但你 就是搜不著。

Java虛擬機規范概要

    研究Java虛擬機,首先要了解Sun的Java虛擬機規范。現在,該實現版本很多,如比較有名的Sun、IBM、BEA、Apple、HP、MS、 Apache Harmony。它們都實現了JVM規范,但有各自擴展。譬如,針對IBM虛擬機的堆碎片導致OutOfMemory(OOM),在Sun的虛擬機上就不 會發生。Sun的JVM有maxPermSize的概念,IBM就沒有,如果你設置這個參數,虛擬機根本就啟動不了。

    比較有意思的是,學Java,就一定要了解各種規范,這和MS的風格很不一樣。Sun總是在定義一些規范,實現都留給各廠商。我們除了理解規范本身外,一 定要理解規范和實現之間的關系,譬如JDBC規范和JDBC驅動的關系,它們是怎么組合到一起的。要是你用過php的xml解析庫,或db函數,就會體會 深刻,它們可沒有什么規范可言,所以每個數據庫廠商的db函數用法都不一樣。我推薦大家研讀一下HSQLDB的jdbc和Tomcat的servlet相 關實現,因為我認為它們還是比較好懂的。

    JVM規范只是定義一個虛擬機該做什么,但它并沒有要求你該怎么做。例如我們最常見的Servlet規范,在該規范中,有 HttpServletRequest、HttpServletResponse,HttpSession等接口,但它們的實現都留給了各個容器廠商。遺 憾的是,規范留下的空白,會把我們這些開發人員給整慘了:容器間移植有時候就是惡夢。譬如J2EE并沒有SSO規范,但它很重要,我以前專門針對它做過 WebSphere AppServer和Weblogic AppServer的SSO項目,差別還是不小,不過還是有點共通,那就是都遵循JAAS規范。


JVM的結構

    從功能上分,Java虛擬機主要由六個部分組成,可以分成三類:
第一類:JVM API:就是我們最常用的Java API,它是開發人員和Java交互的入口,它主要是JAVA_HOME/jre/lib下的運行時類庫rt.jar和編譯相關的tools.jar

第二類:JVM內部組件
類裝載器(ClassLoader):將Byte Array的 .class文件裝載、鏈接和初始化。
內存管理(Memory Managent):為對象分配內存,以及釋放內存。后者就是垃圾回收Garbage Collector(GC)。由于JVM最復雜的、最影響性能的就是GC,所以內存管理一般就指垃圾回收。
診斷接口(Diagostics Interface):這主要體現在JVMTI(jdk1.4下的JVMPI和JVMDI),它主要用來診斷程序的問題和性能,一般提供給工具廠商實現。如eclispe IDE下的debug功能,Jprofiler性能調優工具。
類解釋器(Interpreter):解釋裝載進虛擬機的class對象,包括JIT等特性相關。

第三類:平臺相關接口(Platform Interface):主要為了跨操作系統平臺重用JVM代碼,不過,它和我們開發人員關系不大。

    在以上六個組件中,我們開發人員最關心的是ClassLoaderGC, 用Java做系統框架、容器和它們密切相關。做業務系統時一些基礎代碼也和它們打交道,譬如最常用的Class.forName(), Thread.currentThread.getContextClassLoader()。我們仔細想想,為什么是上面兩個問題?因為,它和我們 class的整個生命周期最為相關:怎么將一個class和相關class加載進來,class實例什么時候創建,什么時候被銷毀?
所以,下面的部分我們要專門討論這些問題。

ClassLoader

    JVM主要有三類ClassLoader:Bootstrap、Extention、Application,該三類ClassLoader從上到下是分級(hierarchy)結構,遵循代理模型(Delegation Model)。
Tip:大家可以看看sun.misc.Launcher的源碼,Bootstrap和Extention就在該文件里。該src可以在sun的網站上下載該壓縮包,約60M(jdk-1_5_0-src-scsl.zip),它不在jdk自帶的那個src.zip里。

    Bootstrap ClassLoader:也稱為primordial(root) class loader。主要是負責裝載jre/lib下的jar文件,當然,你也可以通過-Xbootclasspath參數定義。該ClassLoader不能 被Java代碼實例化,因為它是JVM本身的一部分。

    Extention ClassLoader:該ClassLoader是Bootstrap classLoader的子class loader。它主要負責加載jre/lib/ext/下的所有jar文件。只要jar包放置這個位置,就會被虛擬機加載。一個常見的、類似的問題是,你 將mysql的低版本驅動不小心放置在這兒,但你的Web應用程序的lib下有一個新的jdbc驅動,但怎么都報錯,譬如不支持JDBC2.0的 DataSource,這時你就要當心你的新jdbc可能并沒有被加載。這就是ClassLoader的delegate現象。常見的有log4j、 common-log、dbcp會出現問題,因為它們很容易被人塞到這個ext目錄,或是Tomcat下的common/lib目錄。

    Application ClassLoader:也稱為System ClassLoaer。它負責加載CLASSPATH環境變量下的classes。缺省情況下,它是用戶創建的任何ClassLoader的父 ClassLoader,我們創建的standalone應用的main class缺省情況下也是由它加載(通過Thread.currentThread().getContextClassLoader()查看)。
我們實際開發中,用ClassLoader更多時候是用其加載classpath下的資源,特別是配置文件,如ClassLoader.getResource(),比FileInputStream直接。

ClassLoader是一種分級(hierarchy)的代理(delegation)模型。
Delegation: 其實是Parent Delegation,當需要加載一個class時,當前線程的ClassLoader首先會將請求代理到其父classLoader,遞歸向上,如果該 class已經被父classLoader加載,那么直接拿來用,譬如典型的ArrayList,它最終由Bootstrap ClassLoader加載。并且,每個ClassLoader只有一個父ClassLoader。
Class查找的位置和順序依次是:Cache、parent、self。
Hierarchy: 上面的delegation已經暗示了一種分級結構,同時它也說明:一個ClassLoader只能看到被它自己加載的classes,或是看到其父 (parent) ClassLoader或祖先(ancestor) ClassLoader加載的Classes。
在一個單虛擬機環境下,標識一個類有兩個因素:class的全路徑名、該類的ClassLoader。

    我碰到的一個典型的例子是:在做WAS的SSO開發時,由于我們的類是由WAS在啟動時加載,該ClassLoader比下面的部署的Applicaton的ClassLoader的級別高。所以,在我們自己的類中沒法用到應用程序的連接池,必須自建。
代 理模型是Java安全模型的保證。譬如,我們自己寫一個String.java,并且編譯、package到自己的java.lang包下。按照代理模 型,當前線程的ClassLoader會將其代理到父ClassLoader,父ClassLoader(最終會是Bootstrap)會找到 rt.jar下的String.class,也就是說我們的String.class不會搗亂。

自定義ClassLoader
    我們前面說過,自定義ClassLoader的缺省父ClassLoader是Application ClassLoader。一般的應用開發用不到它,但我們最好理解。因為在內存泄漏查找、應用程序部署出問題時,很多都和它有關。
譬 如,內存泄漏是怎么產生的?這就涉及到ClassLoader和Class的生命周期。我曾經碰到這樣一個問題:我們的程序用到了Webwork和 Spring框架,當部署到Tomcat下時沒有任何問題,但部署到WAS下,報告找不到Webwork的xml的DTD文件,而且Spring的日志也 總是失效。Why?因為解析xml dtd時,用的是IBM的Xerces,不是我們的。而Spring日志問題是因為應用程序用的是WAS的Common-log.jar,而不是我們的。 將應用的ClassLoader從默認的Parent-First,改成Parent-Last就可以解決,不過我們項目中用到其它庫,又發生了其它問 題。

一般來說,用到自定義ClassLoader有三種情況:
1、應用框架可以自己控制Classes的目錄,并且自動部署。
我讀過Jive公司的Wildfire(著名的即時通訊服務器),它自己有一套應用框架,非常靈活,遵循該框架插件規范的的第三方的plug-in放置在指定目錄可以自動部署,實現某些擴展功能,如文件傳輸、語音聊天。
2、區分用戶代碼
這被廣泛應用在Servlet容器和類似容器,譬如EJB Container設計中,大家看到Tomcat下有common、server、share三個目錄吧(ClassLoader順序從左到有),另外也有用戶應用的WEB-INF目錄,它是我們自己開發的。
3、允許Classes卸載
如 果沒有自定義的ClassLoader,那么我們自己應用中的classes永遠都不能被卸載,因為這些類被Application ClassLoader加載后cache起來了,我們的classes一直對該ClassLoader有引用,而該系統級的ClassLoader永遠都 不會被卸載,除非JVM shutdown了。JSP和Servlet的動態部署就用到這個特性。

待續.......

Note: 還有JVM運行時(Runtime)架構,ClassLoader加載class過程沒有總結,這兩部分我覺得太重要了,但內容太多,寫不完啊。
這部分內容,《Inside Java Virtual Machine》講解非常清楚,BEA的官方網站這部分也非常不錯,要理解深刻,我建議結合JProfiler工具,非常直觀。

待續.......





【IT168 技術文檔】

    續寫這篇文章,已經過去一個半月了。直到現在,系統一直運行平穩。
先說說我接手這項工作的經歷 吧:該項目大部分是06年10月就部署在客戶那邊了,到07年3月份,WAS宕機問題實在無法忍受,我才加入進來,前半年有另外一位同事斷斷續續處理,但 對問題一直都無可奈何,而且項目負責人也沒有引起足夠的重視。可想而知,最后付出的代價是非常慘重的。在這近半年的時間內,服務器宕機63次。每次宕機 時,WAS的JVM會dump出一個heapdump.phd文件(heap快照),然后JVM就死掉了,當然,此時WAS也停止了響應。一般我們的做法 是重啟,最后是干脆AIX每天晚上定時重啟。有時候一天還死多次。大家見附件的截圖(all-GC.png)。這是我接手后,用IBM的分析工具得到的截 圖。對截圖的分析,留給后面對應的部分吧。
服務器不穩定、宕機問題,拖延到最后,客戶憤怒了,公司高層也害怕了,部門還專門成立了八人攻關組。當然了,我當時的壓力也非常大,因為我是技術負責人,也就是實實在在干活、想主意的。
服務器診斷那段時間,從前到后,我們也是沿著一條線走下來,雖然最后發現很多路都走不通。現在就按這個思路,也就是時間先后一步步敘述吧。我想,大家如果也碰到類似應用服務器診斷,應該思路差不多。

術語說明:
IBM Websphere Application Server:WAS,WebSphere本身是一個平臺,產品家族
OutOfMemoryError:OOM,內存泄漏,內存溢出
Gabage Collection:GC,自動垃圾回收
Content Management System:CMS,就是給新聞類門戶網站編輯們用的系統

我們診斷大體上經歷了以下幾個階段:
1、按Job調度線程池引起內存泄漏診斷:因為很多次OOM是發生在某個特定時候,譬如14:30、22:40左右。
2、按應用程序引起內存泄漏診斷:用JProfiler等工具探測:因為總是發生OOM。
3、分離WAS懷疑有OOM的應用:因為每個WAS應用太多,20來個,混一起沒法定位。
4、用IBM官方heap、GC分析工具。以及和IBM技術支持聯系。WAS、AIX參數優化。
5、隔離出WAS超級惡魔程序:一個CMS產品。
6、WAS、AIX參數優化、設置。

我們走到第5步時,才出現效果。計算一下,那時已經過去一個月了。服務器宕機、系統不穩定,在這個驗收的時候,客戶已經忍無可忍,以致后來的每一次行動都得膽戰心驚得去做。

一、按Job調度線程池導致內存泄漏診斷
    因為從我們WAS的日志(默認是native_stderr.log)來看,最近半年的宕機時間都有一個明顯時間規律。見附件截圖Job1-1.png。
    我想,做過Java服務器性能調優的朋友,都知道在Web容器里面啟線程池是個不太好的做法,因為Web容器本身有一個線程池,譬如Servlet線程池 (Tomcat默認起25個),而自啟的線程池很容易導致Servlet線程管理混亂,最終導致GC問題。我們的現象似乎和那很符合。如果我們沿著這個思 路做下去,具體怎樣實施呢?

    我們的WAS上部署了20個左右的Web應用,譬如Lucene全文檢索、B2B行業數據同步等,都是通過Quartz的Job調度做的,當然還有很多其 它調度。當時,由我負責,通知相關負責人,將定時調度暫時去掉。觀察了幾天,后來發現問題依然存在,不過時間有點隨機了。
不過,最后還是發現OOM不是由Job調度引起的。
也 就是說,我們這個方案是失敗的。而且,我們的很多想法都是臆測的,沒有可靠的根據,也沒有方向,再加上我是第一次處理這種問題,這導致后來查找問題的艱 難。但是,仔細想想,我們又能拿什么做依據呢?出現OOM錯誤,我想大多數人想到的,除了JVM參數設置,就是內存泄漏。其實,OOM發生有很多種情況, 在IBM、Sun、BEA等不同虛擬機上,因為GC機制不一樣,所以原因一般都不同,容易定位難度也不一樣。下文會談到。
于是,我們干脆釜底抽薪分析問題吧:用JProfiler檢測。


二、按應用程序導致內存泄漏診斷,JProfiler檢測

    如果遇到OOM問題,我想大家都會想到內存檢測工具,現在最可靠的還是下面三種分析工具:Borland 的Optimizeit Suite,Quest的JProbe,ej-technologies的JProfiler。但面臨三個問題:
1、三個都是商業產品,公司暫時沒有買,必須自己下載,而且要找序列號。
2、工具必須支持AIX5.3+JDK1.42+WAS6.0,不是Windows平臺。
3、工具必須在客戶真實環境下部署,對客戶的業務不能有沖擊,也就是說部署測試工具前,必須做個大量測試,對工具非常熟練,遇到意外可以立即恢復現場。
Note:項目上線后,而不是測試或試運行階段遇到此類問題,非常考驗人;另外一個,就是性能和可伸縮性問題,很可能把整個項目給毀了。

    當我決定要這么做后,就立即動手查閱這些工具的官方文檔,用emule下載,最終都下載到了。試用后,最終選擇了JProfiler4.03,比起其它工 具,它界面美觀、清晰、功能強大、集成度高(Heap,Memory,CPU,Thread都統一了)。另外,JProbe沒有AIX版本,這也是放棄它 的一個原因。

    JVM的Profiler原理,都是通過JVM內置的的標準C語言Profiler 接口收集數據,然后通過Profiler工具的客戶端展現。也就是說各廠商的Profiler工具,都有兩個部分,一個部分是Profiler Agent,和JVM綁定,負責收集JVM內部數據,譬如方法調用次數、耗費時間等;另外一個部分就是Profiler front-end。通過Profiler工具的自定義local或remote協議傳輸到front-end,其實,我們最常用的JavaIDE的 debug功能就是在此基礎上的(JPDA)。(JProfiler的截圖http://www.ej-technologies.com/products/jprofiler/screenshots.html )。
下面是Sun官方文檔:
JDK1.42及以前是JVM PI:
http://java.sun.com/j2se/1.4.2/docs/guide/jvmpi/jvmpi.html
JDK1.5是JVM TI:
http://java.sun.com/j2se/1.5.0/docs/guide/jvmti/jvmti.html
具體到JProfiler的配置上,專門針對JDK1.4和1.5的JVM配置差別很大。

我用的JProfiler是4.31版本,透露給大家一個萬能序列號吧(這東西不太好找),對各版本應該都支持。深入了解Java,這類工具是不可少的:
Name: License for You
Lincese Code: A-G667#42616F-10kg1r134fq9m#2217

為了保證真實環境的檢測成功,我做了大量的試驗,譬如:
1、Windows系列的本地、遠程測試。
2、AIX的遠程測試。
3、Tomcat5.0、WebLogic8.14、WebSphere6.02,以及上述兩種方式的組合測試,排列組合,場景不下10個。
當時也參閱了大量JVM文檔,JProfiler官方幾百頁英文文檔,輔助的JProbe對照。而且也制造過內存泄漏造成的OOM場景。
當然,要是在幾個月前,在客戶那邊部署的測試環境時,就進行測試該多好啊。

    在公司內部,我用JProfiler測試了我們當時部署的幾個應用,沒有發現內存泄漏,所以,我們最懷疑的是就是CMS系統。因為出問題的那個WAS上它 占去了90%的負荷(我們有多臺AIX、WAS服務器)。該CMS超級龐大,感覺著名的賽迪網就用它,當時該CMS廠商給我們部署都花了快一個月。所以再 重新部署一套測試環境也挺困難。另外,CMS提供商不給lisence。現在測試,客戶早就對我們惱火了,當然不怎么配合,這對我們工作的開展就有很大的 挑戰。

    在大致可以確定萬無一失的情況下,我們最終決定在客戶的真實環境下測試。也就是讓JProfiler的agent端直接在WAS的JVM里面啟動(北京IDC),然后遠程(大連)監控。
本 來該模式在另外幾個應用的測試都通過了(因為北京IDC那邊好幾臺AIX服務器)。但一部署上,客戶的一些編輯用CMS時就感覺超級慢,盡管我們用了 JProfiler的最小負載模式。半個小時后,客戶實在無法忍受,打電話過來,又把我們部長和經理訓了一頓,還要寫書面報告。我們被迫馬山中止測試,恢 復現場。

    雖然JProfiler也支持客戶那邊的環境,但還是有bug,至少負載一高就有嚴重的性能問題,幾乎導致系統掛起,這是我當時沒有料到的。 JProfiler一啟動,WAS的啟動時間就由原來的3分鐘降到10分鐘,而且系統響應明顯變慢,我們具體的環境如下(排列組合恐怕不下20種):
1、AIX5.3,Power PC64位(不是32位)
2、WebSphere6.0
3、IBM JVM1.42
4、Remote 模式

    我后來仔細讀了一下JProfiler的changeLog,發現對上面的環境支持不夠也很正常,因為官方在最近的4.3.x版本下陸續有對IBM JVM和Websphere6.0的features和bug fix:http://www.ej-technologies.com/download/jprofiler/changelog.html

    進行到這一步,我忽然覺得無計可施了 ,此時已經過了三周。
上面的策略,我認為是很正統的處理方法。誰怪我們當初項目上線時沒有進行充分的測試呢?其實,這類測試沒做也正常,OOM這類問題誰都無法預測。

到這個時候,我想肯定有人會問我?你知道導致JVM的OOM有幾種情況嗎?在當時,我想到了以下五種:
1、JVM的heap最小、最大值沒有設,或不合理。
2、JVM的maxPermSize沒有設置(這個IBM的JVM沒有,一設置JVM就起不來)。
對于Sun和BEA的JVM,以上兩種參數設置,可以排除90%以上的非程序內存溢出導致的OOM。
3、程序內存泄漏
4、有的JVM,當在80%的CPU時間內,不能GC出2%的heap時,也發生OOM(IBM的JVM就是,但我沒有驗證)
5、JVM本身內存泄漏(JVM有bug不是不可能)

    現在的難題是,如果是那個可怕的CMS程序本身有內存溢出,在產品環境下,我們怎么去驗證?我們自己開發的10來個web應用,測試并不是很難,在公司測 試都可以。但是,我現在最想解決的,就是CMS測試的問題。而且,在我們那種環境下,該CMS產品供應商并沒有透露成功案例。

其實,最后發現,并不是內存泄漏造成的,因為我們的heap走勢都很平穩。納悶的是,有1000M的heap,每次在heap只被占用400來M時就發生OOM,沒有任何預兆。大家猜猜,會是什么原因 ?這個問題我到后面相關章節再說吧。

既然我們所有的矛頭都指向那個可怕的CMS系統,現在就是考慮隔離的問題。如果我們驗證這個問題是CMS造成的, 那么大部分責任就應該由CMS廠商承擔
既然CMS我們不敢移(費勁,而且客戶在正式用),那我就移我們開發的10來個web系統吧。

三、移出除CMS系統以外的所有應用
說起來容易啊,做呢? 隔離(移動)工作由我負責,具體涉及到10來個相關負責人。
轉移工作,必須處理好很多問題,就說幾個印象最深的吧:
1、某些應用,如Blog和BBS,都涉及到文件、圖片上傳目錄和產品本身的環境,如 JDBC連接池、Cache位置。
2、目標服務器本身的環境,WAS安裝環境、網絡等。
3、移植時的先后順序、調度,各應用內部本身的約束關系。
4、移植后的測試。
當然,還有一個最嚴峻的問題,客戶允許我們這么做嗎?對它們目前運行的系統有多大影響?風險如何評估?

    這個工作持續了一天,已經完成了80%的工作,到第二天,客戶又惱火了:WAS又宕機了。
為什 么?這確實是WAS的一個bug:WAS的后臺隨便一操作,heap就會突然上升幾百M,導致JVM內存不夠。不過WAS撐住的話,過半小時后就會降下 來,我估計是WAS后臺對用戶操作狀態、文件都緩存到Session里面。你們可以檢查類似這樣的一個文件夾:d:\IBM\WebSphere\ AppServer\profiles\AppSrv01\wstemp,我不知道為什么WAS不主動去清除它,它偷偷的就上升到幾個G,系統硬盤可能不 久就后就會空間不足,WAS莫名遲緩、最后死掉。聽過WAS6.0以前這個目錄更夸張。大家見我附件的截圖WAS_Console.png那個尖峰。

咋辦?經理也已經不敢讓我們繼續鋌而走險了。這個方案最終又以失敗告終

    不過,最后我們還是發現問題出在CMS上。我們以前把這個問題向CMS技術支持反映,有大量依據和現象,并且把相關日志都給它們。過了兩天,他們最后竟然只回了一句話“從給我的兩個日志來看,沒有找到任何與XXX有關的東西....”。TMD!我真的很生氣 ,它們的產品都折磨我們半年之久了。不過,看他們對IBM的WAS和JVM也不懂,我也就不想再說什么了。下面是我的郵件,公司機密部分都隱去了:

引用
    附件是我們這段時間服務器宕機的日志。我們用IBM Pattern Modeling and Analysis Tool for Java Garbage Collector Version 1.3.2分析了一下虛擬機日志,沒有發現是內存泄漏導致;用IBM HeapAnalyzer Version 1.4.4 分析heap文件,也沒有發現很可疑的內存泄漏。

    我想以前你們也這樣做過,現在我們分析錯誤日志,發現有一個現象,在宕機時,總是找不到文件,我看就是Websphere或是AIX IO資源不夠,不知道是什么導致的。但是,我們自己的應用,基本上沒有什么IO,除了一次load幾個配置文件。不過,我覺得你們WCM的IO操作挺多 的,不知道你對日志有什么新的發現。
客觀的說,這幾個月來,宕機那臺服務器,除了你們的XXX,就以論壇和blog為主,而且他們都是開源的。在頻繁宕機的06年11月份,我們的論壇和blog還沒有上線。現在我們不得不每天晚上11點定時重啟,但這也不是長久之計。
現在,我們進行分離遇到很大阻力,原來想把你們的XXX單獨分離出來,在當前的環境下,不是很現實,如安裝、測試(負載、定時服務),所以現在分離我們自己的應用,但當前在產品環境下,客戶方阻力也很大。
希望盡快能夠得到你們的問題建議和方案。

    文中說到了IBM的兩個分析工具,這也是我們后來的救星:我們就是需要這種離線分析工具,因為實時檢測已經證明不現實。但我始終對該分析出來的結果抱懷疑態度,直到我去深入IBM的JVM以及和IBM的技術支持交流......

柳暗花明啊 ,至少看到了一點希望,不過最后我們還是失望而返。


四、用IBM的HeapAnalyzer和GarbageCollector檢測

    找到這兩個工具,已經是夠費勁了,因為以前找的IBM HeapRoot工具,讓我對這類工具很失望。而且,這兩個工具,只有在IBM的Techinical Support網站能夠搜索到,但很不容易,因為那兩個工具,并不是象IBM的Websphere產品那樣宣傳,它只在IBM Techinical Support文章的某些角落里出現。要知道,Techinical Support是IBM很重要的收入來源,這類文檔,他們并不會讓你很輕易就拿到,比起BEA WLS的支持網站dev2dev差遠了。
具體 診斷細節我就不詳述了。我認為,IBM的WAS或JVM出了性能和OOM問題,這兩個工具是最有效的,而且是離線分析工具,比起那些實時Profiler 工具,某些場合有絕對的優勢:譬如我們目前的產品環境,你只能分析宕機后的日志,實時分析前面已經驗證是不可行的。
從日志分析,我們最終得出結論,我們購買的CMS系統有嚴重的碎片(大對象)問題,而該問題是OOM的罪魁禍首,而且IBM工程師也得出了同一結論。不過,在起先我們得出這一結論一周后,我還始終不相信heap碎片會導致OOM,直到IBM工程師總是向我強調。

    我想很多人也是不太相信,因為大多數人用的都是Sun的JVM,譬如Windows、Solaris上的hotspot。而且,Sun JVM出問題,如果是配置的問題,一般通過配置heap最大最小值,以及maxPermSize都可以解決。Heap碎片導致的OOM,只有BEA的 JRockit和IBM JVM上發生,不過JRockit有專門文檔說明,而且很容易找到(就在jdk的文檔里面)。

    配置heap最小最大值,我想大多數人都有經驗。對于Sun的JVM來 說,一般可以設置heap最大最小值一致,也是推薦的做法。因為它的GC策略默認是復制、分代算法。也就是說,它會將heap分成不同的幾個區,譬如 Solaris JVM中最上面有兩個大小相等的區。GC時刻,將一個區的存活對象復制到另外一個對等區,垃圾對象就算遺棄了。這樣在heap里面,就不存在碎片問題。另 外,根據Java對象的存活期不同,將之轉移到不同的區(Tenured區),存活最長的在最底部(火車算法),這也就是分代模型。具體請參考官方文檔:http://java.sun.com/docs/hotspot/gc1.4.2/

    對于maxPermSize(Permanent Generation),主要和那些加載到JVM里面的Java Class對象相關,它的空間不是在Java Heap里面分配。如果你當前的heap有1000M,permSize是200M,那么JVM至少占用1200M。
在 這個空間內的對象的生存期和JVM是一樣的,譬如JDK的核心類庫,它們被System Classloader加載到JVM的Method Area(方法區)后,就不會被GC掉的,這些對象一般是Class對象,而不是普通的實例對象,也就是JVM的元數據。我們在用反射時經常用到它們。所 以,對于現在象Spring、Hibernate這些框架經常通過反射創建實例,可能對maxPermSize要求就大了,缺省的64M很多時候是不夠 的,特別是對于應用服務器里的應用,象JSP就會產生和加載很多classes。不過,如果是它導致的OOM,一般會有類似 perm size提示。

    但是,對于IBM的JVM,情況就完全不一樣。它的默認GC策略并沒有采取復制、分代。這個可以從GC日志分析出來。它不像Sun的JVM那樣,有個單獨 的方法區,它的方法區就放在Java Heap里面。JVM規范里面并沒有要求方法區的必須存放的位置,因為它只是一個JVM實現問題。

    在IBM的JVM里面,這些對象一般分配在稱為k-cluster和p-cluster里(cluster又是屬于Heap),而后者一般是臨時在 heap里面申請。并且,這些cluster是不能GC,或是被移動重排的(Compact過程)。這就導致Java Heap里面就如同馬蜂窩,但不同的蜂孔又不能合并,于是,當我們程序里面產生一個大對象,譬如2M的數組(數組必須分配在連續的內存區)時,就沒有可分 配空間了,于是就報告OOM。這些不能被移動的cluster就稱為所謂的碎片。此時,JVM的Heap利用率可能不到50%。
當然,通過一定時期的GC日志,可以計算出cluster的合理大小(專門在Java Heap的底部),另外,還可以為這些大對象專門分配大對象區的(超過64k的對象)。

    通過上面的理論介紹,我想大家一定知道了為什么IBM的JVM里面不推薦heap的最大最小值相同,因為這樣碎片問題會非常嚴重:如果我們每次大對象申請 內存時,heap都擴展5%,譬如50M,碎片問題很大程度上可以避開,程序性能也高些(尋找可用空隙和分配耗時,以及每次GC時間拉長)。
以上的具體闡述,請參考我在上文推薦的幾個URL,另外再推薦三個寶貴的鏈接:
http://www-1.ibm.com/support/docview.wss?rs=180&context=SSEQTP&q1=fragmentation&uid=swg21176363&loc=en_US&cs=utf-8&lang=en
http://www-900.ibm.com/cn/support/viewdoc/detail?DocId=2447476A10000(IBM 技術支持告訴我的,太重要了!)
http://www-900.ibm.com/cn/support/viewdoc/detail?DocId=2847476B08000

    我想大家應該會問:我怎么能夠肯定我的OOM問題是heap碎片造成的呢?下面的方法可以驗證。
在OOM 發生時,JVM會產生一個heapdump文件。然后用GarbageCollector分析出該OOM發生時刻,JVM去申請的空間,譬如約235k。 此時,你再用HeapAnalyzer去分析此時的heap快照里面的gap size大小(空隙大小)和各自的可用數目。你會發現,大于235k的空隙個數為0。這就是碎片導致OOM的證據。

另外,有人會問:我懷疑我的OOM是因為程序內存泄漏造成的,怎么去驗證

    你可以用HeapAnalyzer分析發生OOM時刻的heap快照,工具會羅列出哪些對象懷疑有內存泄漏,譬如Cache對象都非常大(但你可以確定它 不是內存泄漏)。另外,分析這次宕機(從這次虛擬機啟動到宕機這段時間)的heap走勢,如果曲線明顯是向上傾斜,也就是那種典型的內存泄漏圖,就有可能 是內存泄漏。當然,還必須結合heap快照。
內存持續上升在JVM開始一段時間很正常,因為JVM對第一次訪問到的Class 對象,譬如一個典型的Web應用,就有jdk的class、Spring或Hibernate的class對象,它們都會被緩存下來 (ClassLoader原理),一般均不會被GC。當大多數class對象緩存差不多(當然還可能有一些Singleton對象,不過不怎么占分量), JVM的Heap就平穩了,呈一水平波浪或鋸齒線。
如果可以用JProfiler這類工具實時監控,就更容易確診了。

經過一番周折,我們終于看到了一線希望了

    在一定的準備后,我們決定對WAS進行性能調優了。WAS的調優參數,可以分為兩個部分:JVM級別和WAS級別:
JVM:主要是GC和Heap。
WAS:Thread Pool,JDBC DataSource。
當然要調節,你需要明白你的目標是什么,調節依據是什么,怎么計算,絕對不是憑空想象的,譬如heap最小值1024M,日志證明,該參數非常不適合我們的環境。具體細節,留給后文吧。

戰戰兢兢地,中午12:00,我們給產品環境下的WAS調節參數、重啟,同時優化了AIX的IO相關參數。

    我試著設置了一下JVM的k-cluster和p-cluster。下午15:00左右,WAS掛了,AIX也掛了。這下麻煩可大了。我們都慌了,馬山客 戶的老總就來電話了,一陣嘩嘩啦啦。實在無奈,讓客戶那邊工作人員通知機房(服務器托管處)工作人員重啟AIX。我也不得不強行更改剛才的參數,立即設為 另外一個值。
其實,我把那個兩個cluster值確實設置太大了,我把它們設置為推薦值的5倍,譬如p-cluster是65k×110%×5。另外一個愚蠢的設置就是把最小heap設置為2048M(AIX有4G內存)。
后來我恢復到約正常的值,也就是去掉那個cluster的5,另外分配了一個30%的大對象區(如果1000M的heap,就是700M+300M)。

就這樣,系統持續正常運行了三天,以前可是一天一down。當在三天后再次宕機時,我們都沒有自信了 。不得不通過AIX的cron,繼續每天深夜11點的WAS定時重啟。
不過,那次宕機,包括以后的幾次宕機,再也沒有出現OOM錯誤了,但系統依然不穩定。雖然我可以說OOM問題解決了,但領導和客戶需要的并不是這種結果。

其實,在這個時候,我們已經發現我們系統的四大問題:
1、WAS和JVM參數:OOM問題
2、AIX的IO和Paging Spacing不足:AIX日志后來顯示錯誤
3、AIX的WAS分區空間不夠:WAS的日志膨脹一周就把那個opt分區塞滿了。
4、應用程序的JDBC連接池:我們20來個應用,一個20 connections,DB2數據庫有時被撐死。

也就是說,我們最初在客戶那兒部署時,用的默認值根本不行。而且,部署涉及多人,人員之間出現斷層。如果我們只是按OOM,無疑是走入死胡同,必須全局考慮!
但 是,項目組實力薄弱,公司范圍內就沒有對AIX精通的。不過項目組原來有一個搞銀行系統,在AIX下開發,就他熟悉些。我當時對AIX也比較陌生,你們從 Linux轉到AIX,你就知道它有多別扭了。命令都自定一套(也許因為是Unix元老吧),那個shell也超級別扭,而且參考書特少。不是自詡,我兩 年前負責一個高負載的Linux服務器管理一年多,也是玩得很轉的。

就這樣,他負責AIX的相關問題,我負責WAS相關的。
但是,現實環境,已經不允許我們再試驗下去了 。我們必須找到一條絕對可靠的對策!
這就是下文的CMS系統大遷移,服務器再次優化。


五、隔離CMS系統,服務器優化
    從前面的介紹,大家應該記得,我們開始是固定CMS,分離其它應用,但遭遇失敗。現在是反過來,干脆把CMS系統趕出WAS平臺

說實話,項目經理做這個決定,我認為已經是鼓出很大勇氣了

    當時我們想在一個備用AIX機上安裝CMS產品測試,但最后還是沒有做成:
CMS這類文章發布系統很難安裝,也不好測試,又沒有liscence,而且還有一堆準備工作。絕對沒有著名的openCMS安裝那么簡單,當然功能遠遠比它復雜。而且,我們當時也低估了后來的工作,總覺得問題好解決。

    在很遙遠的06年中期,CMS廠商在客戶那邊一臺AIX的Tomcat上部署了一套CMS產品。但當時客戶執意要求將其跑在WAS上,也就是現在的情況。 最開始,客戶還要求我們必須用WAS的集群(我們買的就是WAS的ND版),無奈該CMS不支持。要是集群,又是死傷一遍。其實,現在想想,我們當時太被 動,CMS這種東西,就供公司的幾十個編輯用,一個普通Tomcat就完全夠用。而且,把它和面向公網的Internet應用混在一起,完全沒有必要。也 許,被動是因為我的實力造成的。

我們決定背水一戰時,已經做過周密的計劃:某年某月某日晚上8:00......
CMS產品負責人現場切換
xx(我)負責WAS相關參數調整
yy負責AIX參數。
zz負責應用的測試
…..

總之,該行動涉及到客戶方、產品提供商、公司高層、項目組。每個人都密切關注,不下20人。每個人都守在電腦前,隨時聽候調遣,當天晚上,我們都沒有準備回家睡覺,大家齊心協力。

真沒想到,整個式切換工作,一個小時就順利完成 !第二天,客戶編輯打開瀏覽器,她們一定想不到昨晚大家準備經歷一場廝殺….

系統持續平穩地運行了一周,然后是漫長的五一,我回湖北黃岡老家休息了八天。回來時,一切依舊。

當天晚上,我們這邊主要做了兩項工作:
1、JVM的Heap參數,共五個。
2、AIX的IO、Paging Space等共六個。
當然還有其他人的工作,譬如測試、監控。

    還有一個非常重要的方面:JDBC連接池。我們原來是在每個Web應用里面獨立設置,這樣20來個應用就有幾百個DB連接,一不小心DB就給撐死。現在統 一交由WAS內置DataSource處理,總共連接不到30個。其實,我們項目開始部署時,就是這樣做的,但當時WAS內置的DataSource對 JTA(XA)支持有bug (這個和IBM技術支持確認過,但他們沒有給予很好的解決方案),不過Datasource還是配好的。

    但是這個工作已經屬于WAS性能優化的主題了,而且優化值必須持續觀察一段時間,通過專門的分析工具來計算。
優化本身,是一項很考驗人的工作,我就簡單說一下最實用方法吧,也許是專門針對IBM的產品。
1、清理歸零WAS日志。然后啟動WAS,生成日志(-verbose:gc默認是開的)
2、讓WAS持續運行約兩周,讓JVM Heap占用曲線平穩一段時間即可(用IBM的Garbage Collector分析觀察)。
3、 在AIX的shell里,產生heapdump.phd文件,也就是heap快照。命令:kill -3 pid (pid是WAS的PID,通過ps –ef查看),觀察heap當時的碎片情況,是否需要單獨分大對象區(一般不需要設置),特別是那個方法區Class對象大小(p-cluster參 數)。
4、通過GC工具,觀察GC平均時間、Heap實際占用情況。Note: GC是一個Stop The World過程,也就是說GC時系統對外不響應,多CPU也不例外。看你的應用實際需求了,GC持續時間和頻率是矛盾的,另外還有性能考慮。一般Web應 用,我想讓GC持續時間(Pause time)調節到合理值就ok了,譬如0.2到0.4s。
5、根據3可以算出k-cluster值,它是工具推薦值的110%。
6、Heap的最小值是程序剛啟動不久的占用值,譬如320M。切記:IBM JVM初始值太大非常不好。
7、 Heap的最大值是系統平穩后的100/70。也就是說如果最大值是1000M,那么應該平穩時是700M,還有30%的空余。IBM的JVM默認情下的 碎片問題,WAS控制臺下操作Heap猛增這種bug,你不得不堤防。Heap最大值不設,AIX下的WAS肯定OOM。

當然啦,我沒有考慮到大對象區的計算(雖然我們的應用設置了專門的大對象區),包括IBM JVM支持的分代GC、并行GC,Heap每次expand百分比等。那些情況我們一般不常用,譬如,你的AIX平臺一般不是16CPU吧?

一口氣寫到現在,我忽然覺得該收尾了。下面就說說我對這類工作的整體看法吧。
1、盡量在項目測試和試運行的時候就進行壓力、性能測試,當正式投入使用后,如果發現類似問題,代價非常大。躲過算你運氣好,一般來說,可能你們系統沒多少人用,也不是核心業務系統,譬如一般的電子政務。
2、千萬不要低估了技術風險,用IBM的系列產品尤其要慎重,出問題一定不要忘了技術支持。而且,查資料時,建議用google English,因為象WebSphere這類問題,很少有中文資料。
3、程序部署環境建立時,就要考慮到日后的正式環境,譬如AIX的Paging Space、IO、分區大小,默認值往往是不行的,而且在產品環境下改這些值,往往非常難。
4、 在項目開發初期,就考慮到日志的問題,因為它分散到每個方法內,必須慎重定義好debug、info、warn、error級別,不要隨便忽視異常 (catch里面不記錄),到真正程序出問題時,它就是我們的最重要的依據之一。當然這主要是功能性問題診斷。另外對于高負載網站,日志文件往往非常大, 各級別日志千萬不要混在一起,否則找問題就很困難了。
5、怎么說呢,別死扣技術,以為什么都可以通過技術解決。你看我們最大的問題就是把 CMS給移到Tomcat下。你要是問我,為什么CMS產品會導致系統這么多的問題,我也不知道,到那時候,我確實也不想深入。我只要知道,趕出你這個應 用,我的系統就好控制多了。而且,那個CMS系統,在Tomcat下,就是跑得服服帖帖的,非常穩定。難度是可惡的WAS?不過那CMS,據IBM工程 師,包括我們二次開發,都覺得夠爛了,每個jsp頁面都打開、關閉DB connection(7年前的jsp開發模式),還有那么嚴重的大對象問題。

    好了,以上總結的幾點可能也不充分、深入,但如果你仔細讀我這篇文章,應該有自己的想法。畢竟,只有經過思考的東西,才會屬于自己。