IT168 技術(shù)文檔】近段時(shí)間,我們項(xiàng)目中用到的WebSphere應(yīng)用服務(wù)器(WAS),但在客戶(hù)的production環(huán)境下極不穩(wěn)定,經(jīng)常宕機(jī)。給客戶(hù)造成非 常不好的影響,同時(shí),也給項(xiàng)目組很大壓力。為此,我們花了近一個(gè)月時(shí)間對(duì)其診斷,現(xiàn)在基本上穩(wěn)定了,需要繼續(xù)觀察一段時(shí)間?,F(xiàn)在我主要將工作做一個(gè)階段性 的總結(jié)。 
我們的產(chǎn)品環(huán)境是:WAS6.0+DB2 8.1+AIX5.3+RS/6000。在該產(chǎn)品環(huán)境下,出現(xiàn)的問(wèn)題非常多,現(xiàn)象如下: 
WAS經(jīng)常不穩(wěn)定、宕機(jī)幾乎一天一次,經(jīng)常報(bào)告OutOfMemory(內(nèi)存泄漏嗎?NO)。 
DB2連接數(shù)過(guò)大,有時(shí)把DB2撐死,有時(shí)也把AIX撐死。 
AIX虛擬內(nèi)存報(bào)錯(cuò)、分頁(yè)報(bào)錯(cuò)、IO也報(bào)錯(cuò)、還有很多其它莫名奇妙的錯(cuò)。

    總是,每次問(wèn)題發(fā)生的現(xiàn)象和理論上的總是不一致,導(dǎo)致我們不知道從何入手,也無(wú)從檢測(cè)自己的優(yōu)化參數(shù)。咨詢(xún)過(guò)多次IBM技術(shù)支持,只解決了某些局部問(wèn)題。 
雖然問(wèn)題依然存在,但我想,解決問(wèn)題的思路、特別是理論基礎(chǔ),還是有一些規(guī)律和原則。

對(duì)于WAS這塊,我近段時(shí)間的主要時(shí)間集中在以下幾個(gè)方面(時(shí)間順序): 
1、Java性能監(jiān)測(cè)工具:Jprofiler,也用到Jprobe。后來(lái)發(fā)現(xiàn)Jprofiler在AIX下幾乎不可用。 
2、IBM Java虛擬機(jī)和WAS技術(shù)細(xì)節(jié),特別是IBM JVM的GC原理,我發(fā)現(xiàn)它和sun、bea的差別很大。 
3、IBM的heap分析器Heap Analyzer、GCCollector。這兩個(gè)事后監(jiān)測(cè)工具非常實(shí)用,特別是我們的產(chǎn)品運(yùn)行環(huán)境,非測(cè)試環(huán)境。 
4、某些Application的懷疑和診斷。 
5、AIX診斷,我?guī)缀鯖](méi)有這個(gè)能力,只能常規(guī)監(jiān)測(cè)一下,需另請(qǐng)高人。

我打算將本文分成以下幾個(gè)部分總結(jié): 
JVM原理、IBM JVM的GC策略和調(diào)優(yōu)。 
Jprofiler和IBM工具的實(shí)際體會(huì) 
WAS的診斷體會(huì)和AIX調(diào)優(yōu)

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

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

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

Java虛擬機(jī)規(guī)范概要 

    研究Java虛擬機(jī),首先要了解Sun的Java虛擬機(jī)規(guī)范?,F(xiàn)在,該實(shí)現(xiàn)版本很多,如比較有名的Sun、IBM、BEA、Apple、HP、MS、 Apache Harmony。它們都實(shí)現(xiàn)了JVM規(guī)范,但有各自擴(kuò)展。譬如,針對(duì)IBM虛擬機(jī)的堆碎片導(dǎo)致OutOfMemory(OOM),在Sun的虛擬機(jī)上就不 會(huì)發(fā)生。Sun的JVM有maxPermSize的概念,IBM就沒(méi)有,如果你設(shè)置這個(gè)參數(shù),虛擬機(jī)根本就啟動(dòng)不了。

    比較有意思的是,學(xué)Java,就一定要了解各種規(guī)范,這和MS的風(fēng)格很不一樣。Sun總是在定義一些規(guī)范,實(shí)現(xiàn)都留給各廠(chǎng)商。我們除了理解規(guī)范本身外,一 定要理解規(guī)范和實(shí)現(xiàn)之間的關(guān)系,譬如JDBC規(guī)范和JDBC驅(qū)動(dòng)的關(guān)系,它們是怎么組合到一起的。要是你用過(guò)php的xml解析庫(kù),或db函數(shù),就會(huì)體會(huì) 深刻,它們可沒(méi)有什么規(guī)范可言,所以每個(gè)數(shù)據(jù)庫(kù)廠(chǎng)商的db函數(shù)用法都不一樣。我推薦大家研讀一下HSQLDB的jdbc和Tomcat的servlet相 關(guān)實(shí)現(xiàn),因?yàn)槲艺J(rèn)為它們還是比較好懂的。 

    JVM規(guī)范只是定義一個(gè)虛擬機(jī)該做什么,但它并沒(méi)有要求你該怎么做。例如我們最常見(jiàn)的Servlet規(guī)范,在該規(guī)范中,有 HttpServletRequest、HttpServletResponse,HttpSession等接口,但它們的實(shí)現(xiàn)都留給了各個(gè)容器廠(chǎng)商。遺 憾的是,規(guī)范留下的空白,會(huì)把我們這些開(kāi)發(fā)人員給整慘了:容器間移植有時(shí)候就是惡夢(mèng)。譬如J2EE并沒(méi)有SSO規(guī)范,但它很重要,我以前專(zhuān)門(mén)針對(duì)它做過(guò) WebSphere AppServer和Weblogic AppServer的SSO項(xiàng)目,差別還是不小,不過(guò)還是有點(diǎn)共通,那就是都遵循JAAS規(guī)范。


JVM的結(jié)構(gòu) 

    從功能上分,Java虛擬機(jī)主要由六個(gè)部分組成,可以分成三類(lèi): 
第一類(lèi):JVM API:就是我們最常用的Java API,它是開(kāi)發(fā)人員和Java交互的入口,它主要是JAVA_HOME/jre/lib下的運(yùn)行時(shí)類(lèi)庫(kù)rt.jar和編譯相關(guān)的tools.jar

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

第三類(lèi):平臺(tái)相關(guān)接口(Platform Interface):主要為了跨操作系統(tǒng)平臺(tái)重用JVM代碼,不過(guò),它和我們開(kāi)發(fā)人員關(guān)系不大。

    在以上六個(gè)組件中,我們開(kāi)發(fā)人員最關(guān)心的是ClassLoaderGC, 用Java做系統(tǒng)框架、容器和它們密切相關(guān)。做業(yè)務(wù)系統(tǒng)時(shí)一些基礎(chǔ)代碼也和它們打交道,譬如最常用的Class.forName(), Thread.currentThread.getContextClassLoader()。我們仔細(xì)想想,為什么是上面兩個(gè)問(wèn)題?因?yàn)?,它和我?class的整個(gè)生命周期最為相關(guān):怎么將一個(gè)class和相關(guān)class加載進(jìn)來(lái),class實(shí)例什么時(shí)候創(chuàng)建,什么時(shí)候被銷(xiāo)毀? 
所以,下面的部分我們要專(zhuān)門(mén)討論這些問(wèn)題。

ClassLoader 

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

    Bootstrap ClassLoader:也稱(chēng)為primordial(root) class loader。主要是負(fù)責(zé)裝載jre/lib下的jar文件,當(dāng)然,你也可以通過(guò)-Xbootclasspath參數(shù)定義。該ClassLoader不能 被Java代碼實(shí)例化,因?yàn)樗荍VM本身的一部分。

    Extention ClassLoader:該ClassLoader是Bootstrap classLoader的子class loader。它主要負(fù)責(zé)加載jre/lib/ext/下的所有jar文件。只要jar包放置這個(gè)位置,就會(huì)被虛擬機(jī)加載。一個(gè)常見(jiàn)的、類(lèi)似的問(wèn)題是,你 將mysql的低版本驅(qū)動(dòng)不小心放置在這兒,但你的Web應(yīng)用程序的lib下有一個(gè)新的jdbc驅(qū)動(dòng),但怎么都報(bào)錯(cuò),譬如不支持JDBC2.0的 DataSource,這時(shí)你就要當(dāng)心你的新jdbc可能并沒(méi)有被加載。這就是ClassLoader的delegate現(xiàn)象。常見(jiàn)的有l(wèi)og4j、 common-log、dbcp會(huì)出現(xiàn)問(wèn)題,因?yàn)樗鼈兒苋菀妆蝗巳竭@個(gè)ext目錄,或是Tomcat下的common/lib目錄。

    Application ClassLoader:也稱(chēng)為System ClassLoaer。它負(fù)責(zé)加載CLASSPATH環(huán)境變量下的classes。缺省情況下,它是用戶(hù)創(chuàng)建的任何ClassLoader的父 ClassLoader,我們創(chuàng)建的standalone應(yīng)用的main class缺省情況下也是由它加載(通過(guò)Thread.currentThread().getContextClassLoader()查看)。 
我們實(shí)際開(kāi)發(fā)中,用ClassLoader更多時(shí)候是用其加載classpath下的資源,特別是配置文件,如ClassLoader.getResource(),比FileInputStream直接。

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

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

自定義ClassLoader 
    我們前面說(shuō)過(guò),自定義ClassLoader的缺省父ClassLoader是Application ClassLoader。一般的應(yīng)用開(kāi)發(fā)用不到它,但我們最好理解。因?yàn)樵趦?nèi)存泄漏查找、應(yīng)用程序部署出問(wèn)題時(shí),很多都和它有關(guān)。 
譬 如,內(nèi)存泄漏是怎么產(chǎn)生的?這就涉及到ClassLoader和Class的生命周期。我曾經(jīng)碰到這樣一個(gè)問(wèn)題:我們的程序用到了Webwork和 Spring框架,當(dāng)部署到Tomcat下時(shí)沒(méi)有任何問(wèn)題,但部署到WAS下,報(bào)告找不到Webwork的xml的DTD文件,而且Spring的日志也 總是失效。Why?因?yàn)榻馕鰔ml dtd時(shí),用的是IBM的Xerces,不是我們的。而Spring日志問(wèn)題是因?yàn)閼?yīng)用程序用的是WAS的Common-log.jar,而不是我們的。 將應(yīng)用的ClassLoader從默認(rèn)的Parent-First,改成Parent-Last就可以解決,不過(guò)我們項(xiàng)目中用到其它庫(kù),又發(fā)生了其它問(wèn) 題。

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