每一個Java開發者都知道Java字節碼是執行在JRE((Java Runtime Environment Java運行時環境)上的。JRE中最重要的部分是Java虛擬機(JVM),JVM負責分析和執行Java字節碼。Java開發人員並不須要去關心JVM是如何運行的。在沒有深刻理解JVM的狀況下,許多開發者已經開發出了很是多的優秀的應用以及Java類庫。不過,若是你瞭解JVM的話,你會更加了解Java的,而且你會輕鬆解決那些看似簡單可是無從下手的問題。html
所以,在這篇文件裏,我會闡述JVM是如何運行的,包括它的結構,它如何去執行字節碼,以及按照怎樣的順序去執行,同時我還會給出一些常見錯誤的示例以及對應的解決辦法。最後,我還會講解Java 7中的一些新特性。java
JRE是由Java API和JVM組成的。JVM的主要做用是經過Class Loader來加載Java程序,而且按照Java API來執行加載的程序。web
虛擬機是經過軟件的方式來模擬實現的機器(好比說計算機),它能夠像物理機同樣運行程序。設計虛擬機的初衷是讓Java可以經過它來實現WORA(Write Once Run Anywhere 一次編譯,處處運行),儘管這個目標如今已經被大多數人忽略了。所以,JVM能夠在不修改Java代碼的狀況下,在全部的硬件環境上運行Java字節碼。apache
Java虛擬機的特色以下:編程
雖然是Sun公司開發了Java,可是全部的開發商均可以開發而且提供遵循Java虛擬機規範的JVM。正是因爲這個緣由,使得Oracle HotSpot和IBM JVM等不一樣的JVM可以並存。Google的Android系統裏的Dalvik VM也是一種JVM,雖然它並不遵循Java虛擬機規範。和基於棧的Java虛擬機不一樣,Dalvik VM是基於寄存器的架構,所以它的Java字節碼也被轉化成基於寄存器的指令集。bootstrap
爲了保證WORA,JVM使用Java字節碼這種介於Java和機器語言之間的中間語言。字節碼是部署Java代碼的最小單位。數組
在解釋Java字節碼以前,咱們先經過實例來簡單瞭解它。這個案例是一個在開發環境出現的真實案例的總結。緩存
一個一直運行正常的應用忽然沒法運行了。在類庫被更新以後,返回下面的錯誤。安全
[java] view plain copy性能優化
應用的代碼以下,並且它沒有被改動過。
[java] view plain copy
更新後的類庫的源代碼和原始的代碼以下。
[java] view plain copy
簡而言之,以前沒有返回值的addUser()被改修改爲返回一個User類的實例的方法。不過,應用的代碼沒有作任何修改,由於它沒有使用addUser()的返回值。咋一看,com.nhn.user.UserAdmin.addUser()方法彷佛仍然存在,若是存在的話,那麼怎麼還會出現NoSuchMethodError的錯誤呢?
上面問題的緣由是在於應用的代碼沒有用新的類庫來進行編譯。換句話來講,應用代碼彷佛是調了正確的方法,只是沒有使用它的返回值而已。無論怎樣,編譯後的class文件代表了這個方法是有返回值的。你能夠從下面的錯誤信息裏看到答案。
[java] view plain copy
NoSuchMethodError出現的緣由是「com.nhn.user.UserAdmin.addUser(Ljava/lang/String;)V」方法找不到。注意一下」Ljava/lang/String;」和最後面的「V」。在Java字節碼的表達式裏,」L<classname>;」表示的是類的實例。這裏表示addUser()方法有一個java/lang/String的對象做爲參數。在這個類庫裏,參數沒有被改變,因此它是正常的。最後面的「V」表示這個方法的返回值。在Java字節碼的表達式裏,」V」表示沒有返回值(Void)。綜上所述,上面的錯誤信息是表示有一個java.lang.String類型的參數,而且沒有返回值的com.nhn.user.UserAdmin.addUser方法沒有找到。
由於應用是用以前的類庫編譯的,因此返回值爲空的方法被調用了。可是在修改後的類庫裏,返回值爲空的方法不存在,而且添加了一個返回值爲「Lcom/nhn/user/User」的方法。所以,就出現了NoSuchMethodError。
注:
這個錯誤出現的緣由是由於開發者沒有用新的類庫來從新編譯應用。不過,出現這種問題的大部分責任在於類庫的提供者。這個public的方法原本沒有返回值的,可是後來卻被修改爲返回User類的實例。很明顯,方法的簽名被修改了,這也代表了這個類庫的後向兼容性被破壞了。所以,這個類庫的提供者應該告知使用者這個方法已經被改變了。
咱們再回到Java字節碼上來。Java字節碼是JVM很重要的部分。JVM是模擬執行Java字節碼的一個模擬器。Java編譯器不會直接把高級語言(例如C/C++)編寫的代碼直接轉換成機器語言(CPU指令);它會把開發者能夠理解的Java語言轉換成JVM可以理解的Java字節碼。由於Java字節碼自己是平臺無關的,因此它能夠在任何安裝了JVM(確切地說,是相匹配的JRE)的硬件上執行,即便是在CPU和OS都不相同的平臺上(在Windows PC上開發和編譯的字節碼能夠不作任何修改就直接運行在Linux機器上)。編譯後的代碼的大小和源代碼大小基本一致,這樣就能夠很容易地經過網絡來傳輸和執行編譯後的代碼。
Java class文件是一種人很難去理解的二進文件。爲了便於理解它,JVM提供者提供了javap,反彙編器。使用javap產生的結果是Java彙編語言。在上面的例子中,下面的Java彙編代碼是經過javap -c對UserServiceadd()方法進行反彙編獲得的。
[java] view plain copy
在這段Java彙編代碼中,addUser()方法是在第四行的「5:invokevitual#23″進行調用的。這表示對應索引爲23的方法會被調用。索引爲23的方法的名稱已經被javap給註解在旁邊了。invokevirtual是Java字節碼裏調用方法的最基本的操做碼。在Java字節碼裏,有四種操做碼能夠用來調用一個方法,分別是:invokeinterface,invokespecial,invokestatic以及invokevirtual。操做碼的做用分別以下:
Java字節碼的指令集由操做碼和操做數組成。相似invokevirtual這樣的操做數須要2個字節的操做數。
用更新過的類庫來編譯上面的應用代碼,而後反編譯它,將會獲得下面的結果。
[java] view plain copy
你會發現,對應索引爲23的方法被替換成了一個返回值爲」Lcom/nhn/user/User」的方法。
在上面的反彙編代碼裏,代碼前面的數字代碼什麼呢?
它表示的是字節偏移。大概這就是爲何運行在JVM上面的代碼成爲Java「字節」碼的緣由。簡而言之,Java字節碼指令的操做碼,例如aload_0,getfield和invokevirtual等,都是用一個字節的數字來表示的(aload_0=0x2a,getfield=0xb4,invokevirtual=0xb6)。由此可知Java字節碼指令的操做碼最多有256個。
aload_0和aload_1這樣的指令不須要任何操做數。所以,aload_0指令的下一個字節是下一個指令的操做碼。不過,getfield和invokevirtual指令須要2字節的操做數。所以,getfiled的下一條指令是跳過兩個字節,寫在第四個字節的位置上的。十六進制編譯器裏查看字節碼的結果以下所示。
[plain] view plain copy
在Java字節碼裏,類的實例用字母「L;」表示,void 用字母「V」表示。經過這種方式,其餘的類型也有對應的表達式。下面的表格對此做了總結。
表一:Java字節碼中的類型表達式
Java Bytecode | Type | Description |
B | byte | signed byte |
C | char | Unicode character |
D | double | double-precision floating-point value |
F | float | single-precision floating-point value |
I | int | integer |
J | long | long integer |
L<classname> | reference | an instance of class <classname> |
S | short | signed short |
Z | boolean | true or false |
[ | reference | one array dimension |
下面的表格給出了字節碼錶達式的幾個實例。
表二:Java字節碼錶達式範例
Java Code | Java Bytecode Expression |
double d[ ][ ][ ]; | [[[D |
Object mymethod(int I, double d, Thread t) | (IDLjava/lang/Thread;)Ljava/lang/Object; |
想了解更多細節的話,參考《The java Virtual Machine Specification,第二版》中的「4.3 Descriptors"。想了解更多的Java字節碼的指令的話,參考《The Java Virtual Machined Instruction Set》的「6.The Java Virtual Machine Instruction Set"。
在講解Java class文件格式以前,咱們先看看一個在Java Web應用中常常出現的問題。
當咱們編寫完jsp代碼,而且在Tomcat運行時,Jsp代碼沒有正常運行,而是出現了下面的錯誤。
[java] view plain copy
在不一樣的Web服務器上,上面的錯誤信息可能會有點不一樣,不過有有一點確定是相同的,它出現的緣由是65535字節的限制。這個65535字節的限制是JVM規範裏的限制,它規定了一個方法的大小不能超過65535字節。
下面我會更加詳細地講解這個65535字節限制的意義以及它出現的緣由。
Java字節碼裏的分支和跳轉指令分別是」goto"和"jsr"。
[java] view plain copy
這兩個指令都接收一個2字節的有符號的分支跳轉偏移量作爲操做數,所以偏移量最大隻能達到65535。不過,爲了支持更多的跳轉,Java字節碼提供了"goto_w"和"jsr_w"這兩個能夠接收4字節分支偏移的指令。
[java] view plain copy
有了這兩個指令,索引超過65535的分支也是可用的。所以,Java方法的65535字節的限制就能夠解除了。不過,因爲Java class文件的更多的其餘的限制,使得Java方法仍是不能超過65535字節。
爲了展現其餘的限制,我會簡單講解一下class 文件的格式。
Java class文件的大體結構以下:
[java] view plain copy
上面的內容是來自《The Java Virtual Machine Specification,Second Edition》的4.1節「The ClassFile Structure"。
以前反彙編的UserService.class文件反彙編的結果的前16個字節在十六進制編輯器中以下所示:
ca fe ba be 00 00 00 32 00 28 07 00 02 01 00 1b
經過這些數值,咱們能夠來看看class文件的格式。
javap簡要地給出了class文件的一個可讀形式。當你用"java -verbose"命令來分析UserService.class時,會輸出以下的內容:
[java] view plain copy
javap輸出的內容太長,我這裏只是提出了整個輸出的一部分。整個的輸出展現了constant_pool裏的不一樣信息,以及方法的內容。
關於方法的65565字節大小的限制是和method_info struct相關的。method_info結構包含Code,LineNumberTable,以及LocalViriable attribute幾個屬性,這個在「javap -verbose"的輸出裏能夠看到。Code屬性裏的LineNumberTable,LocalVariableTable以及exception_table的長度都是用一個固定的2字節來表示的。所以,方法的大小是不能超過LineNumberTable,LocalVariableTable以及exception_table的長度的,它們都是65535字節。
許多人都在抱怨方法的大小限制,並且在JVM規範裏還說名了」這個長度之後有可能會是可擴展的「。不過,到如今爲止,尚未爲這個限制作出任何動做。從JVM規範裏的把class文件裏的內容直接拷貝到方法區這個特色來看,要想在保持後向兼容性的同時來擴展方法區的大小是很是困難的。
若是由於Java編譯器的錯誤而致使class文件的錯誤,會怎麼樣呢?或者,由於網絡傳輸的錯誤致使拷貝的class文件的損壞呢?
爲了預防這種場景,Java的類裝載器經過一個嚴格並且慎密的過程來校驗class文件。在JVM規範裏詳細地講解了這方面的內容。
注意
咱們怎樣可以判斷JVM正確地執行了class文件校驗的全部過程呢?咱們怎麼來判斷不一樣提供商的不一樣JVM實現是符合JVM規範的呢?爲了可以驗證以上兩點,Oracle提供了一個測試工具TCK(Technology Compatibility Kit)。這個TCK工具經過執行成千上萬的測試用例來驗證一個JVM是否符合規範,這些測試裏面包含了各類非法的class文件。只有經過了TCK的測試的JVM才能稱做JVM。
和TCK類似,有一個組織JCP(Java Community Process;http://jcp.org)負責Java規範以及新的Java技術規範。對於JCP而言,若是要完成一項Java規範請求(Java Specification Request, JSR)的話,須要具有規範文檔,可參考的實現以及經過TCK測試。任何人若是想使用一項申請JSR的新技術的話,他要麼使用RI提供許可的實現,要麼本身實現一個而且保證經過TCK的測試。
Java編寫的代碼會按照下圖的流程來執行:
圖 1: Java代碼執行流程
類裝載器裝載負責裝載編譯後的字節碼,並加載到運行時數據區(Runtime Data Area),而後執行引擎執行會執行這些字節碼。
Java提供了動態的裝載特性;它會在運行時的第一次引用到一個class的時候對它進行裝載和連接,而不是在編譯期進行。JVM的類裝載器負責動態裝載。Java類裝載器有以下幾個特色:
每一個類裝載器都有一個本身的命名空間用來保存已裝載的類。當一個類裝載器裝載一個類時,它會經過保存在命名空間裏的類全侷限定名(Fully Qualified Class Name)進行搜索來檢測這個類是否已經被加載了。若是兩個類的全侷限定名是同樣的,可是若是命名空間不同的話,那麼它們仍是不一樣的類。不一樣的命名空間表示class被不一樣的類裝載器裝載。
下圖展現了類裝載器的代理模型。
圖 2: 類加載器代理模型
當一個類裝載器(class loader)被請求裝載類時,它首先按照順序在上層裝載器、父裝載器以及自身的裝載器的緩存裏檢查這個類是否已經存在。簡單來講,就是在緩存裏查看這個類是否已經被本身裝載過了,若是沒有的話,繼續查找父類的緩存,直到在bootstrap類裝載器裏也沒有找到的話,它就會本身在文件系統裏去查找而且加載這個類。
相似於web應用服務(WAS)之類的框架會用這種結構來對Web應用和企業級應用進行分離。換句話來講,類裝載器的代理模型能夠用來保證不一樣應用之間的相互獨立。WAS類裝載器使用這種層級結構,不一樣的WAS供應商的裝載器結構有稍許區別。
若是類裝載器查找到一個沒有裝載的類,它會按照下圖的流程來裝載和連接這個類:
圖 3: 類加載的各個階段
每一個階段的描述以下:
JVM規範定義了上面的幾個任務,不過它容許具體執行的時候可以有些靈活的變更。
圖 4: 運行時數據區
運行時數據區是在JVM運行的時候操做系統所分配的內存區。運行時內存區能夠劃分爲6個區域。在這6個區域中,一個PC Register,JVM stack 以及Native Method Statck都是按照線程建立的,Heap,Method Area以及Runtime Constant Pool都是被全部線程公用的。
圖 5: JVM堆棧
--- 棧幀(stack frame):每當一個方法在JVM上執行的時候,都會建立一個棧幀,而且會添加到當前線程的JVM堆棧上。當這個方法執行結束的時候,這個棧幀就會被移除。每一個棧幀裏都包含有當前正在執行的方法所屬類的本地變量數組,操做數棧,以及運行時常量池的引用。本地變量數組的和操做數棧的大小都是在編譯時肯定的。所以,一個方法的棧幀的大小也是固定不變的。
--- 局部變量數組(Local variable array):這個數組的索引從0開始。索引爲0的變量表示這個方法所屬的類的實例。從1開始,首先存放的是傳給該方法的參數,在參數後面保存的是方法的局部變量。
--- 操做數棧(Operand stack):方法實際運行的工做空間。每一個方法都在操做數棧和局部變量數組之間交換數據,而且壓入或者彈出其餘方法返回的結果。操做數棧所需的最大空間是在編譯期肯定的。所以,操做數棧的大小也能夠在編譯期間肯定。
如今咱們再回過頭來看看以前反彙編的字節碼。
[java] view plain copy
把上面的反彙編代碼和咱們平時所見的x86架構的彙編代碼相比較,咱們會發現這二者的結構有點類似,都使用了操做碼;不過,有一點不一樣的地方是Java字節碼並不會在操做數裏寫入寄存器的名稱、內存地址或者偏移量。以前已經說過,JVM用的是棧,它不會使用寄存器。和使用寄存器的x86架構不一樣,它本身負責內存的管理。它用索引例如15和23來代替實際的內存地址。15和23都是當前類(這裏是UserService類)的常量池裏的索引。簡而言之,JVM爲每一個類建立了一個常量池,而且這個常量池裏保存了實際目標的引用。
每行反彙編代碼的解釋以下:
下圖能夠幫助你更好地理解上面的內容。
6: 裝載到運行時數據區的Java字節碼示例
順便提一下,在這個方法裏,局部變量數組沒有被修改。因此上圖只顯示了操做數棧的變化。不過,大部分的狀況下,局部變量數組也是會改變的。局部變量數組和操做數棧之間的數據傳輸是使用經過大量的load指令(aload,iload)和store指令(astore,istore)來實現的。
在這個圖裏,咱們簡單驗證了運行時常量池和JVM棧的描述。當JVM運行的時候,每一個類的實例都會在堆上進行分配,User,UserAdmin,UserService以及String等類的信息都會保存在方法區。
經過類裝載器裝載的,被分配到JVM的運行時數據區的字節碼會被執行引擎執行。執行引擎以指令爲單位讀取Java字節碼。它就像一個CPU同樣,一條一條地執行機器指令。每一個字節碼指令都由一個1字節的操做碼和附加的操做數組成。執行引擎取得一個操做碼,而後根據操做數來執行任務,完成後就繼續執行下一條操做碼。
不過Java字節碼是用一種人類能夠讀懂的語言編寫的,而不是用機器能夠直接執行的語言。所以,執行引擎必須把字節碼轉換成能夠直接被JVM執行的語言。字節碼能夠經過如下兩種方式轉換成合適的語言。
不過,用JIT編譯器來編譯代碼所花的時間要比用解釋器去一條條解釋執行花的時間要多。所以,若是代碼只被執行一次的話,那麼最好仍是解釋執行而不是編譯後再執行。所以,內置了JIT編譯器的JVM都會檢查方法的執行頻率,若是一個方法的執行頻率超過一個特定的值的話,那麼這個方法就會被編譯成本地代碼。
圖 7:Java編譯器和JIT編譯器
JVM規範沒有定義執行引擎該如何去執行。所以,JVM的提供者經過使用不一樣的技術以及不一樣類型的JIT編譯器來提升執行引擎的效率。
大部分的JIT編譯器都是按照下圖的方式來執行的:
圖 8: JIT編譯器
JIT編譯器把字節碼轉換成一箇中間層表達式,一種中間層的表示方式,來進行優化,而後再把這種表示轉換成本地代碼。
Oracle Hotspot VM使用一種叫作熱點編譯器的JIT編譯器。它之因此被稱做」熱點「是由於熱點編譯器經過分析找到最須要編譯的「熱點」代碼,而後把熱點代碼編譯成本地代碼。若是已經被編譯成本地代碼的字節碼再也不被頻繁調用了,換句話說,這個方法再也不是熱點了,那麼Hotspot VM會把編譯過的本地代碼從cache裏移除,而且從新按照解釋的方式來執行它。Hotspot VM分爲Server VM和Client VM兩種,這兩種VM使用不一樣的JIT編譯器。
Figure 9: Hotspot Client VM and Server VM
Client VM 和Server VM使用徹底相同的運行時,不過如上圖所示,它們所使用的JIT編譯器是不一樣的。Server VM用的是更高級的動態優化編譯器,這個編譯器使用了更加複雜而且更多種類的性能優化技術。
IBM 在IBM JDK 6裏不只引入了JIT編譯器,它同時還引入了AOT(Ahead-Of-Time)編譯器。它使得多個JVM能夠經過共享緩存來共享編譯過的本地代碼。簡而言之,經過AOT編譯器編譯過的代碼能夠直接被其餘JVM使用。除此以外,IBM JVM經過使用AOT編譯器來提早把代碼編譯器成JXE(Java EXecutable)文件格式來提供一種更加快速的執行方式。
大部分Java程序的性能都是經過提高執行引擎的性能來達到的。正如JIT編譯器同樣,不少優化的技術都被引入進來使得JVM的性能一直可以獲得提高。最原始的JVM和最新的JVM最大的差異之處就是在於執行引擎。
Hotspot編譯器在1.3版本的時候就被引入到Oracle Hotspot VM裏了,JIT編譯技術在Anroid 2.2版本的時候被引入到Dalvik VM裏。
注意:
引入一種中間語言,例如字節碼,虛擬機執行字節碼,而且經過JIT編譯器來提高JVM的性能的這種技術以及普遍應用在使用中間語言的編程語言上。例如微軟的.Net,CLR(Common Language Runtime 公共語言運行時),也是一種VM,它執行一種被稱做CIL(Common Intermediate Language)的字節碼。CLR提供了AOT編譯器和JIT編譯器。所以,用C#或者VB.NET編寫的源代碼被編譯後,編譯器會生成CIL而且CIL會執行在有JIT編譯器的CLR上。CLR和JVM類似,它也有垃圾回收機制,而且也是基於堆棧運行。
2011年7月28日,Oracle發佈了Java SE的第7個版本,而且把JVM規也更新到了相應的版本。在1999年發佈《The Java Virtual Machine Specification,Second Edition》後,Oracle花了12年來發布這個更新的版本。這個更新的版本包含了這12年來累積的衆多變化以及修改,而且更加細緻地對規範進行了描述。此外,它還反映了《The Java Language Specificaion,Java SE 7 Edition》裏的內容。主要的變化總結以下:
最大的改變是添加了invokedynamic指令。也就是說JVM的內部指令集作了修改,使得JVM開始支持動態類型的語言,這種語言的類型不是固定的,例如腳本語言以及來自Java SE 7裏的Java語言。以前沒有被用到的操做碼186被分配給新指令invokedynamic,並且class文件格式裏也添加了新的內容來支持invokedynamic指令。
Java SE 7的編譯器生成的class文件的版本號是51.0。Java SE 6的是50.0。class文件的格式變更比較大,所以,51.0版本的class文件不可以在Java SE 6的虛擬機上執行。
儘管有了這麼多的變更,可是Java方法的65535字節的限制仍是沒有被去掉。除非class文件的格式完全改變,否者這個限制未來也是不可能去掉的。
值得說明的是,Oracle Java SE 7 VM支持G1這種新的垃圾回收機制,不過,它被限制在Oracle JVM上,所以,JVM自己對於垃圾回收的實現不作任何限制。也所以,在JVM規範裏沒有對它進行描述。
Java SE 7裏添加了不少新的語法和特性。不過,在Java SE 7的版本里,相對於語言自己而言,JVM沒有多少的改變。那麼,這些新的語言特性是怎麼來實現的呢?咱們經過反彙編的方式來看看switch語句裏的String(把字符串做爲switch()語句的比較對象)是怎麼實現的?
例如,下面的代碼:
[java] view plain copy
由於這是Java SE 7的一個新特性,因此它不能在Java SE 6或者更低版本的編譯器上來編譯。用Java SE 7的javac來編譯。下面是經過javap -c來反編譯後的結果。
[java] view plain copy
在#5和#8字節處,首先是調用了hashCode()方法,而後它做爲參數調用了switch(int)。在lookupswitch的指令裏,根據hashCode的結果進行不一樣的分支跳轉。字符串「abc"的hashCode是96354,它會跳轉到#36處。字符串」123「的hashCode是48690,它會跳轉到#50處。生成的字節碼的長度比Java源碼長多了。首先,你能夠看到字節碼裏用lookupswitch指令來實現switch()語句。不過,這裏使用了兩個lookupswitch指令,而不是一個。若是反編譯的是針對Int的switch()語句的話,字節碼裏只會使用一個lookupswitch指令。也就是說,針對string的switch語句被分紅用兩個語句來實現。留心標號爲#5,#39和#53的指令,來看看switch()語句是如何處理字符串的。
在第#36,#37,#39,以及#42字節的地方,你能夠看見str參數被equals()方法用來和字符串「abc」進行比較。若是比較的結果是相等的話,‘0’會被放入到局部變量數組的索引爲#3的位置,而後跳抓轉到第#61字節。
在第#50,#51,#53,以及#56字節的地方,你能夠看見str參數被equals()方法用來和字符串「123」進行比較。若是比較的結果是相等的話,'1’會被放入到局部變量數組的索引爲#3的位置,而後跳轉到第#61字節。
在第#61和#62字節的地方,局部變量數組裏索引爲#3的值,這裏是'0',‘1’或者其餘的值,被lookupswitch用來進行搜索並進行相應的分支跳轉。
換句話來講,在Java代碼裏的用來做爲switch()的參數的字符串str變量是經過hashCode()和equals()方法來進行比較,而後根據比較的結果,來執行swtich()語句。
在這個結果裏,編譯後的字節碼和以前版本的JVM規範沒有不兼容的地方。Java SE 7的這個用字符串做爲switch參數的特性是經過Java編譯器來處理的,而不是經過JVM來支持的。經過這種方式還能夠把其餘的Java SE 7的新特性也經過Java編譯器來實現。
我不認爲爲了使用好Java必須去了解Java底層的實現。許多沒有深刻理解JVM的開發者也開發出了不少很是好的應用和類庫。不過,若是你更加理解JVM的話,你就會更加理解Java,這樣你會有助於你處理相似於咱們前面的案例中的問題。
除了這篇文章裏提到的,JVM仍是用了其餘的不少特性和技術。JVM規範提供的是一種擴展性很強的規範,這樣就使得JVM的提供者能夠選擇更多的技術來提升性能。值得特別說明的一點是,垃圾回收技術被大多數使用虛擬機的語言所使用。不過,因爲這個已經在不少地方有更加專業的研究,我這篇文章就沒有對它進行深刻講解了。
對於熟悉韓語的朋友,若是你想要深刻理解JVM的內部結構的話,我推薦你參考《Java Performance Fundamental》(Hando Kim,Seoul,EXEM,2009)。這本書是用韓文寫的,更適合你去閱讀。我在寫這本書的時候,參考了JVM規範,同時也參考了這本書。對於熟悉英語的朋友,你能夠找到大量的關於Java性能的書籍。
By Se Hoon Park, Messaging Platform Development Team, NHN Corporation.
英文原文:http://www.cubrid.org/blog/dev-platform/understanding-jvm-internals/