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的動態部署就用到這個特性。