Java虛擬機(jī)的起源與構(gòu)造
當(dāng)我們說(shuō)到“Java”這個(gè)詞的時(shí)候,指的是四個(gè)相互關(guān)聯(lián)的概念:Java語(yǔ)言、Java API、Java Class文件格式、Java虛擬機(jī)。整個(gè)Java體系是基于Java 虛擬機(jī)構(gòu)造的,正因?yàn)槿绱耍拍軐?shí)現(xiàn)Java的安全性和網(wǎng)絡(luò)移動(dòng)性。Java并非是第一個(gè)采用“虛擬機(jī)”概念的體系,但卻是第一個(gè)得到廣泛運(yùn)用的虛擬機(jī)平臺(tái)。 “虛擬”,是一種隔離物理資源與邏輯資源的手段。Java虛擬機(jī)的“虛擬”,則是用來(lái)隔離物理機(jī)器、底層操作系統(tǒng)與Java語(yǔ)言規(guī)范實(shí)現(xiàn)的手段。
雖然Java是一種面向?qū)ο蟮恼Z(yǔ)言,我們平時(shí)大量使用的,是對(duì)象間的多態(tài)、組合(Composition)、委派(Delegation),但當(dāng)我們討論虛擬機(jī)的時(shí)候,我們看見(jiàn)的基本概念卻是“棧(Stack)”和“堆(Heap)”。根據(jù)馮諾依曼的“存儲(chǔ)計(jì)算”模型,所有的代碼都保存在代碼空間中,隨著程序計(jì)數(shù)器指針的變化進(jìn)行程序的執(zhí)行、跳轉(zhuǎn)。Java虛擬機(jī)中沒(méi)有寄存器的概念,方法調(diào)用是采用“棧”進(jìn)行的,這是一種安全、簡(jiǎn)潔的方法。
Java虛擬機(jī)通過(guò)類(lèi)裝載器支持對(duì)類(lèi)的隔離,這也是Java實(shí)現(xiàn)安全性的基礎(chǔ)。每個(gè)類(lèi)都具有自己的命名空間,在具有不同安全級(jí)別的沙箱中運(yùn)行,因此不會(huì)產(chǎn)生低安全級(jí)別的代碼來(lái)越權(quán)訪問(wèn)高級(jí)別代碼的機(jī)會(huì)。類(lèi)裝載器的出現(xiàn)是Java虛擬機(jī)與大部分用C實(shí)現(xiàn)的虛擬機(jī)的顯著不同之處。
Java虛擬機(jī)的另外一個(gè)顯著特點(diǎn)就是實(shí)現(xiàn)了自動(dòng)的垃圾收集。在往常,寫(xiě)程序的時(shí)候要牢記對(duì)象之間的關(guān)聯(lián),在每個(gè)程序塊中假若申請(qǐng)了對(duì)象空間,就必須在出口釋放掉,方法調(diào)用往往同時(shí)也就是對(duì)象的邊界。而自動(dòng)垃圾收集帶給開(kāi)發(fā)者的最大好處,就是可以非常方便地從整體上把系統(tǒng)的對(duì)象組織成一張對(duì)象圖,只需往這張圖中添加對(duì)象,維護(hù)對(duì)象之間的關(guān)聯(lián),卻不需要自己做復(fù)雜的清掃工作。正是有了這種思維單純的對(duì)象圖的支持,OR Mapping(關(guān)系數(shù)據(jù)庫(kù)與對(duì)象映射)技術(shù)在最近得以大行其道,設(shè)計(jì)模式也更容易被Java群體所接受。
虛擬機(jī)的優(yōu)化
1995年第一代的Java出臺(tái)之時(shí),其虛擬機(jī)執(zhí)行是依靠“字節(jié)碼解釋器(Byte Code Interceptor)”的,也就是說(shuō)每條指令都由虛擬機(jī)來(lái)當(dāng)場(chǎng)解釋執(zhí)行,這造成速度令人抓狂地緩慢。更有甚者有人開(kāi)始總結(jié)許多的“速度優(yōu)化經(jīng)驗(yàn)”,比如說(shuō):“盡量把所有的代碼都放在較大的方法中執(zhí)行”與“少用接口”等等,這完全與Java語(yǔ)言的設(shè)計(jì)目的背道而馳,現(xiàn)在看起來(lái)是多么可笑的奇談怪論,當(dāng)時(shí)卻是很多程序員津津樂(lè)道的經(jīng)驗(yàn)之談。無(wú)他,Java本身執(zhí)行太慢了。Java生命的前十分之三就是如此緩慢地渡過(guò)的。
于是,Sun的工程師開(kāi)始拼命想著提高執(zhí)行速度。JIT靜態(tài)編譯器的出現(xiàn)是在1996年十月,Sun放出了第一個(gè)編譯器。JIT編譯器在每段代碼執(zhí)行前進(jìn)行編譯,編譯的結(jié)果為本地靜態(tài)機(jī)器碼,執(zhí)行速度有了質(zhì)的提高。Symantec公司當(dāng)時(shí)憑借其傲人的JIT編譯器,在整個(gè)Java界受到熱烈的追捧。在其后的1998年,Java 1.2發(fā)布的時(shí)候,附帶了JIT編譯器,從此Java的使用者終于可以?huà)侀_(kāi)上面說(shuō)的那些奇怪的“速度優(yōu)化經(jīng)驗(yàn)”了。
JIT靜態(tài)編譯器雖然可以解決一些問(wèn)題,但是性能仍然和C/C++有很大的差距。對(duì)一段程序而言,一名優(yōu)秀的程序員是如何來(lái)改進(jìn)運(yùn)行速度的呢?首先,他不會(huì)傻到把所有的代碼都來(lái)優(yōu)化,他會(huì)觀察、思考到底哪段代碼對(duì)整體性能影響最大?然后集中精力來(lái)優(yōu)化這一段代碼。按照經(jīng)驗(yàn),整個(gè)程序 10%-20%的代碼,會(huì)占據(jù) 80%-90%的運(yùn)行時(shí)間。用這種方法,在同樣的時(shí)間、付出同樣程度的努力后,這名優(yōu)秀的程序員使整個(gè)程序的性能得到了很大程度的優(yōu)化。HotSpot引擎,就是模仿人工的這種方法進(jìn)行優(yōu)化的。在程序運(yùn)行的開(kāi)始,Java代碼仍然解釋執(zhí)行,但HotSpot引擎開(kāi)始進(jìn)行采樣(Profiling)。根據(jù)采樣的結(jié)果,決定某段程序是占用較多運(yùn)行時(shí)間的,就認(rèn)為它是“HotSpot”,它也就是目前程序的瓶頸, 引擎開(kāi)始啟動(dòng)一個(gè)單獨(dú)的線(xiàn)程進(jìn)行優(yōu)化。因?yàn)椴幌笤嫉?JIT編譯器那樣無(wú)差別的編譯所有代碼,HotSpot引擎可以集中精力來(lái)對(duì)HotSpot代碼進(jìn)行深度優(yōu)化,這樣這部分代碼執(zhí)行起來(lái)更加迅捷。之前的靜態(tài)編譯器只能按照預(yù)定的策略進(jìn)行編譯優(yōu)化,而HotSpot引擎的優(yōu)化是基于采樣的結(jié)果的,因此這種方法對(duì)所有的應(yīng)用程序都有效。1999年3月27日,Sun放出了第一個(gè)HotSpot引擎。在隨后的2000年5月的JDK 1.3中,包含了HotSopt引擎,這也使1.3成了一個(gè)具有里程碑意義的發(fā)行版本。到這里,Java的十年生命,已經(jīng)過(guò)去了一半。
HotSpot代表的是一種動(dòng)態(tài)編譯的技術(shù)。對(duì)Java這種大量使用委派、組合等面向?qū)ο筇匦缘某绦騺?lái)說(shuō),動(dòng)態(tài)編譯比起靜態(tài)編譯來(lái)有顯著的優(yōu)勢(shì)。比如Method Inlining。方法的調(diào)用是一個(gè)很耗時(shí)的操作,假若可以把方法調(diào)用直接內(nèi)嵌到調(diào)用者的代碼中,就可以節(jié)省大量的時(shí)間, 這被稱(chēng)為“Method Inlining”。因?yàn)樯婕暗筋?lèi)的重載,靜態(tài)優(yōu)化很難確切知道哪些屬性、方法被重載,因此很難對(duì)method進(jìn)行合并,只好在方法內(nèi)部進(jìn)行靜態(tài)編譯,假若每個(gè)方法都很小,靜態(tài)優(yōu)化能起到的作用也就比較小。而動(dòng)態(tài)編譯因?yàn)榭梢酝耆S時(shí)掌握類(lèi)的重載情況,就可以把相關(guān)的方法合并進(jìn)行深度優(yōu)化。現(xiàn)代的Java程序,特別是在設(shè)計(jì)模式教育得到普及之后,大量使用類(lèi)的繼承、委派,形成了很多短小的方法,動(dòng)態(tài)編譯的優(yōu)勢(shì)就更加明顯。
自從出現(xiàn)了HotSpot之后,整個(gè)Java界為之一振。
最近的五年,就是繼續(xù)優(yōu)化的五年。繼續(xù)進(jìn)行優(yōu)化的方法有幾條路,一是研究新的采樣算法。因?yàn)椴蓸雨P(guān)系到不同的優(yōu)化策略,會(huì)對(duì)整體性能有比較大的影響。二是研究深度優(yōu)化的方法。三是研究垃圾收集的算法。垃圾收集會(huì)帶來(lái)程序短暫的停頓,這會(huì)帶來(lái)負(fù)面的用戶(hù)體驗(yàn)。于是,如何提高垃圾收集的效率,減少延遲,出現(xiàn)了五花八門(mén)的算法,比如漸進(jìn)式收集、火車(chē)算法等。在多處理器的時(shí)候,如何利用多處理器進(jìn)行并行收集也是研究的一個(gè)熱點(diǎn)。這方面,BEA的JRocket走在了前面。
現(xiàn)實(shí)生活中的虛擬機(jī)
最后,讓我們來(lái)盤(pán)點(diǎn)一下目前市面上可見(jiàn)的各個(gè)虛擬機(jī)。
首先要提到的,毫無(wú)疑問(wèn)是Sun的虛擬機(jī)。作為大眾心目中的“官方實(shí)現(xiàn)”,Sun擁有最大的用戶(hù)群,并且擁有“兼容基準(zhǔn)”的地位,其他虛擬機(jī)都必須要考慮和Sun虛擬機(jī)的兼容性問(wèn)題。比如 JRocket就會(huì)在某些特殊情況下表現(xiàn)出和Sun不同的特性,可能對(duì)程序運(yùn)行有影響。不過(guò)Sun也的確沒(méi)有讓廣大用戶(hù)失望,雖然在早期性能比不上Symantec,后來(lái)在1.2 的時(shí)候性能又被IBM超越,但Sun一直在努力革新,特別是 1.4.2之后,性能有了長(zhǎng)足的進(jìn)步。雖然JDK 1.5的虛擬機(jī)在性能上沒(méi)有什么提高,但是增強(qiáng)了穩(wěn)定性,據(jù)說(shuō)修改了8000處bug,真是讓人汗流不止。原來(lái)我們?cè)?.4.2下面一直在享受這么多bug啊。
其次是老牌勁旅IBM。IBM的JDK在1.3的時(shí)代創(chuàng)下了最好的性能記錄,從此樹(shù)立了高端形象。特別是在其WebSphere產(chǎn)品中得到了很好的評(píng)價(jià)。其JDK也是最早支持64bit的JDK之一。到了現(xiàn)在,IBM JDK在高端仍然是和BEA可以一拼的。
然后是后起之秀,BEA的JRocket。說(shuō)到BEA突然在JVM領(lǐng)域一夜之間異軍突起,多少讓人有些瞠目,不過(guò)它采取的戰(zhàn)略特別簡(jiǎn)單:自己沒(méi)有,索性花錢(qián)買(mǎi)了在此領(lǐng)域深有研究的JRocket,在前面加上BEA的標(biāo)志就可以了。JRocket瞄準(zhǔn)高端服務(wù)器市場(chǎng),在多處理器環(huán)境下有不俗的表現(xiàn)。
除此之外,還有幾個(gè)開(kāi)放源代碼的JVM值得一提。首先就是大名鼎鼎的JikesRVM。說(shuō)起其大名,大多數(shù)人都知道Jikes編譯器是 IBM開(kāi)發(fā)的,效率比同等的javac編譯器高得多,很多開(kāi)發(fā)者都使用Jikes編譯器來(lái)取代javac。而JikesRVM則是IBM開(kāi)源出來(lái)的一整套虛擬機(jī)技術(shù),包含了JIT,GC的完整實(shí)現(xiàn),在其網(wǎng)站上也有眾多的論文,實(shí)在是想要深入研究JVM者的絕佳資源(http://jikesrvm.sourceforge.net/)。
Kaffe是一個(gè)老牌的JVM,不過(guò)現(xiàn)在已經(jīng)很少聽(tīng)到了。作者撰寫(xiě)此文時(shí),http://www.kaffe.org/網(wǎng)站已經(jīng)沒(méi)有響應(yīng),也不知道現(xiàn)在的情況如何了。
GNU則有兩個(gè)計(jì)劃:GCJ和GNU classpath。GNU classpath是一個(gè)底層實(shí)現(xiàn),而GCJ是支持java的預(yù)編譯器。
結(jié)束語(yǔ)
時(shí)光流轉(zhuǎn),轟轟烈烈的Java虛擬機(jī)性能爭(zhēng)論仿佛還在耳邊回響,現(xiàn)在新的爭(zhēng)論卻已經(jīng)是“Java的性能是否已經(jīng)超越C/C++”。Joakim Dahlstedt 是 JRockit 的主要架構(gòu)設(shè)計(jì)師之一,他堅(jiān)持認(rèn)為,Java絕不是一種速度慢,效率低的語(yǔ)言,JVM 是一個(gè)關(guān)鍵的組件,確保了系統(tǒng)的部署與運(yùn)行和開(kāi)發(fā)一樣快速、輕松。特別是在目前開(kāi)發(fā)趨勢(shì)是采用大量預(yù)制的框架時(shí),動(dòng)態(tài)編譯有可能比C/C++這樣的靜態(tài)優(yōu)化獲得更好的性能。