Java虛擬機進階之一:Java VM前世今生
目標:
1, Java技術體系,從兩種分類來看。
2, 解釋器和編譯器的概念及配合工作原理。
3, 各種Java虛擬機的介紹。
4, JVM的趨勢:多語言混合編程。
5, 64位虛擬機的優缺點及替代方案。
6, 如何獲取JDK源碼
7,Java和Java虛擬機的關係
前言
Java開發技術本身的一個重要優點導致:在虛擬機層面隱藏了底層技術的複雜性以及機器與操作系統的差異性。
Java虛擬機的原因:爲了達到給所有硬件提供一致的虛擬平臺的目的,犧牲了一些與硬件相關的性能特性。
第1章 走近Java
世界上並沒有完美的程序,但我們並不因此而沮喪,因爲寫程序本來就是一個不斷追求完美的過程。
1.2 Java技術體系
從廣義上講,Clojure、JRuby、Groovy等運行於Java虛擬機上的語言及其相關的程序都屬於Java技術體系中的一員。
僅從傳統意義上來看,Sun官方所定義的Java技術體系包括以下幾個組成部分:
1, Java程序設計語言
2,各種硬件平臺上的Java虛擬機
3, Class文件格式
4,Java API類庫
5,來自商業機構和開源社區的第三方Java類庫
如果按照技術所服務的領域來劃分,或者說按照Java技術關注的重點業務領域來劃分,Java技術體系可以分爲4個平臺,分別爲: Java Card:支持一些Java小程序(Applets)運行在小內存設備(如智能卡)上的平臺。 Java ME(Micro Edition):支持Java程序運行在移動終端(手機、PDA)上的平臺,對Java API有所精簡,並加入了針對移動終端的支持,這個版本以前稱爲J2ME。 Java SE(Standard Edition):支持面向桌面級應用(如Windows下的應用程序)的Java平臺,提供了完整的Java核心API,這個版本以前稱爲J2SE。 Java EE(Enterprise Edition):支持使用多層架構的企業應用(如ERP、CRM應用)的Java平臺,除了提供Java SE API外,還對其做了大量的擴充並提供了相關的部署支持,這個版本以前稱爲J2EE。
1, Java Card
2, Java Me
3, Java SE
4, Java EE
1.3 Java發展史
這個版本中Java虛擬機第一次內置了JIT(Just In Time)編譯器(JDK 1.2中曾並存過3個虛擬機,Classic VM、HotSpot VM和Exact VM,其中Exact VM只在Solaris平臺出現過;後面兩個虛擬機都是內置JIT編譯器的,而之前版本所帶的Classic VM只能以外掛的形式使用JIT編譯器)
Q:什麼是解釋器,什麼是編譯器?
解釋器就是虛擬機將源代碼編譯成一種中間的字節碼(class文件),與機器平臺無關
編譯器就是虛擬機將源代碼編譯成和本地機器平臺相關的機器語言。
Q:什麼是JIT編譯器?
即時編譯器(Just In Time Compiler) 簡稱JIT。
JAVA程序最初是通過解釋器(Interpreter)進行解釋執行的,當JVM發現某個方法或代碼塊運行特別頻繁的時候,就會認爲這是“熱點代碼”(Hot Spot Code)。
爲了提高熱點代碼的執行效率,就會將這些“熱點代碼”編譯成與本地機器相關的機器碼,進行各個層次的優化。 完成這個任務的編譯器就是即時編譯器(JIT)。
Q:解釋器和編譯器之間是如何配合的?
1、當程序需要迅速啓動和執行的時候,解析器首先發揮作用,省去編譯的時間,立即執行。隨着時間的推移,編譯器發揮作用,把越來越多的代碼編譯成本地代碼,獲得更高的執行效率。
2、當機器內存限制比較大,可以用解析方式節約內存,反之可以用編譯提升效率。
3、解析器還可以作爲編譯器的“逃生門”。當例如加載了新類後類型結構發生變化,可以採用逆優化,退回到解析狀態繼續執行。
Oracle公司分別從BEA和Sun中取得了目前三大商業虛擬機的其中兩個:JRockit和HotSpot,
1.4.1 Sun Classic/Exact VM
1996年1月23日,Sun公司發佈JDK 1.0,Java語言首次擁有了商用的正式運行環境,這個JDK中所帶的虛擬機就是Classic VM。這款虛擬機只能使用純解釋器方式來執行Java代碼,如果要使用JIT編譯器,就必須進行外掛。但是假如外掛了JIT編譯器,JIT編譯器就完全接管了虛擬機的執行系統,解釋器便不再工作了。
由於解釋器和編譯器不能配合工作,這就意味着如果要使用編譯器執行,編譯器就不得不對每一個方法、每一行代碼都進行編譯,而無論它們執行的頻率是否具有編譯的價值。基於程序響應時間的壓力,這些編譯器根本不敢應用編譯耗時稍高的優化技術,因此這個階段的虛擬機即使用了JIT編譯器輸出本地代碼,執行效率也和傳統的C/C++程序有很大差距,“Java語言很慢”的形象就是在這時候開始。
Exact VM的虛擬機,它的執行系統已經具備現代高性能虛擬機的雛形:如兩級即時編譯器、編譯器與解釋器混合工作模式等。Exact VM因它使用準確式內存管理(Exact Memory Management,也可以叫Non-Conservative/Accurate Memory Management)而得名,即虛擬機可以知道內存中某個位置的數據具體是什麼類型。譬如內存中有一個32位的整數123456,它到底是一個reference類型指向123456的內存地址還是一個數值爲123456的整數,虛擬機將有能力分辨出來,這樣才能在GC(垃圾收集)的時候準確判斷堆上的數據是否還可能被使用。由於使用了準確式內存管理,Exact VM可以拋棄以前Classic VM基於handler的對象查找方式(原因是進行GC後對象將可能會被移動位置,如果將地址爲123456的對象移動到654321,在沒有明確信息表明內存中哪些數據是reference的前提下,虛擬機是不敢把內存中所有爲123456的值改成654321的,所以要使用句柄來保持reference值的穩定),這樣每次定位對象都少了一次間接查找的開銷,提升執行性能。
1.4.2 Sun HotSpot VM
HotSpot VM的熱點代碼探測能力可以通過執行計數器找出最具有編譯價值的代碼,然後通知JIT編譯器以方法爲單位進行編譯。如果一個方法被頻繁調用,或方法中有效循環次數很多,將會分別觸發標準編譯和OSR(棧上替換)編譯動作。通過編譯器與解釋器恰當地協同工作,可以在最優化的程序響應時間與最佳執行性能中取得平衡,而且無須等待本地代碼輸出才能執行程序,即時編譯的時間壓力也相對減小,這樣有助於引入更多的代碼優化技術,輸出質量更高的本地代碼。
在2008年和2009年,Oracle公司分別收購了BEA公司和Sun公司,這樣Oracle就同時擁有了兩款優秀的Java虛擬機:JRockit VM和HotSpot VM。Oracle公司宣佈在不久的將來(大約應在發佈JDK 8的時候)會完成這兩款虛擬機的整合工作,使之優勢互補。整合的方式大致上是在HotSpot的基礎上,移植JRockit的優秀特性,譬如使用JRockit的垃圾回收器與MissionControl服務,使用HotSpot的JIT編譯器與混合的運行時系統。
1.4.4 BEA JRockit/IBM J9 VM
JRockit VM曾經號稱“世界上速度最快的Java虛擬機”(廣告詞,貌似J9 VM也這樣說過),它是BEA公司在2002年從Appeal Virtual Machines公司收購的虛擬機。BEA公司將其發展爲一款專門爲服務器硬件和服務器端應用場景高度優化的虛擬機,由於專注於服務器端應用,它可以不太關注程序啓動速度,因此JRockit內部不包含解析器實現,全部代碼都靠即時編譯器編譯後執行。除此之外,JRockit的垃圾收集器和MissionControl服務套件等部分的實現,在衆多Java虛擬機中也一直處於領先水平。
1.4.5 Azul VM/BEA Liquid VM
我們平時所提及的“高性能Java虛擬機”一般是指HotSpot、JRockit、J9這類在通用平臺上運行的商用虛擬機,但其實Azul VM和BEA Liquid VM這類特定硬件平臺專有的虛擬機纔是“高性能”的武器。
1.4.6 Apache Harmony/Google Android Dalvik VM
Dalvik VM並不是一個Java虛擬機,它沒有遵循Java虛擬機規範,不能直接執行Java的Class文件,使用的是寄存器架構而不是JVM中常見的棧架構。但是它與Java又有着千絲萬縷的聯繫,它執行的dex(Dalvik Executable)文件可以通過Class文件轉化而來,使用Java語法編寫應用程序,可以直接使用大部分的Java API等。目前Dalvik VM隨着Android一起處於迅猛發展階段,在Android 2.2中已提供即時編譯器實現,在執行性能上有了很大的提高。
1.5.2 混合語言
Java平臺上的多語言混合編程正成爲主流,每種語言都可以針對自己擅長的方面更好地解決問題。
想一下,在一個項目之中,並行處理用Clojure語言編寫,展示層使用JRuby/Rails,中間層則是Java,每個應用層都將使用不同的編程語言來完成,而且,接口對每一層的開發者都是透明的,各種語言之間的交互不存在任何困難,就像使用自己語言的原生API一樣方便,因爲它們最終都運行在一個虛擬機之上
在最近的幾年裏,Clojure、JRuby、Groovy等新生語言的使用人數不斷增長,而運行在Java虛擬機(JVM)之上的語言數量也在迅速膨脹,圖1-4中列舉了其中的一部分。這兩點證明混合編程在我們身邊已經有所應用並被廣泛認可。通過特定領域的語言去解決特定領域的問題是當前軟件開發應對日趨複雜的項目需求的一個方向。
對這些運行於Java虛擬機之上、Java之外的語言,來自系統級的、底層的支持正在迅速增強,以JSR-292爲核心的一系列項目和功能改進(如Da Vinci Machine項目、Nashorn引擎、InvokeDynamic指令、java.lang.invoke包等),推動Java虛擬機從“Java語言的虛擬機”向“多語言虛擬機”的方向發展。
1.5.3 多核並行
早在JDK 1.5就已經引入java.util.concurrent包實現了一個粗粒度的併發框架。而JDK 1.7中加入的java.util.concurrent.forkjoin包則是對這個框架的一次重要擴充。Fork/Join模式是處理並行編程的一個經典方法,
在Java 8中,將會提供Lambda支持,這將會極大改善目前Java語言不適合函數式編程的現狀(目前Java語言使用函數式編程並不是不可以,只是會顯得很臃腫),函數式編程的一個重要優點就是這樣的程序天然地適合並行運行,這對Java語言在多核時代繼續保持主流語言的地位有很大幫助。
1.5.5 64位虛擬機
Java虛擬機也在很早之前就推出了支持64位系統的版本。但Java程序運行在64位虛擬機上需要付出比較大的額外代價:
首先是內存問題,由於指針膨脹和各種數據類型對齊補白的原因,運行於64位系統上的Java應用需要消耗更多的內存,通常要比32位系統額外增加10%~30%的內存消耗;
其次,多個機構的測試結果顯示,64位虛擬機的運行速度在各個測試項中幾乎全面落後於32位虛擬機,兩者大約有15%左右的性能差距。
但是在Java EE方面,企業級應用經常需要使用超過4GB的內存,對於64位虛擬機的需求是非常迫切的,但由於上述原因,許多企業應用都仍然選擇使用虛擬集羣等方式繼續在32位虛擬機中進行部署。
1.6 實戰:自己編譯JDK
想要一探JDK內部的實現機制,最便捷的路徑之一就是自己編譯一套JDK,通過閱讀和跟蹤調試JDK源碼去了解Java技術體系的原理,雖然門檻會高一點,但肯定會比閱讀各種書籍、文章更加貼近本質。另外,JDK中的很多底層方法都是本地化(Native)的,需要跟蹤這些方法的運作或對JDK進行Hack的時候,都需要自己編譯一套JDK。
1.6.1 獲取JDK源碼
獲取OpenJDK源碼有兩種方式:
對於一般讀者,建議採用第二種方式,即直接下載官方打包好的源碼包,讀者可以從Source Bundle Releases頁面(地址:http://jdk7.java.net/source.html)取得打包好的源碼,到本地直接解壓即可。
For MoreDetails(官方文檔):
參看官網文檔java及java vm相關部分,提到一個問題:
Q: Java 和Java虛擬機的關係
Java 虛擬機並不侷限於特定的實現技術、主機硬件和操作系統,Java 虛擬機也不侷限於特定的代碼執行方式,它不強求使用解釋器來執行程序,也可以通過把自己的指令集編譯爲實際 CPU 的指令來實現,它可以通過微代碼(Microcode)來實現,或者甚至直接實現在 CPU 中。
Java 虛擬機與 Java 語言並沒有必然的聯繫,它只與特定的二進制文件格式——Class 文件
格式所關聯,Class 文件中包含了 Java 虛擬機指令集(或者稱爲字節碼、Bytecodes)和符號表,還有一些其他輔助信息。
基於安全方面的考慮,Java 虛擬機要求在 Class 文件中使用了許多強制性的語法和結構化
約束,但任一門功能性語言都可以表示爲一個能被 Java 虛擬機接收的有效的 Class 文件。作爲一個通用的、機器無關的執行平臺,任何其他語言的實現者都可以將 Java 虛擬機作爲他們語言的產品交付媒介。