1、什麼是Java虛擬機
當你談到Java虛擬機時,你多是指:
一、抽象的Java虛擬機規範
二、一個具體的Java虛擬機實現
三、一個運行的Java虛擬機實例
2、Java虛擬機的生命週期
一個運行中的Java虛擬機有着一個清晰的任務:執行Java程序。程序開始執行時他才運行,程序結束時他就中止。你在同一臺機器上運行三個程序,就會有三個運行中的Java虛擬機。
Java虛擬機老是開始於一個main()方法,這個方法必須是公有、返回void、直接受一個字符串數組。在程序執行時,你必須給Java虛擬機指明這個包換main()方法的類名。
Main()方法是程序的起點,他被執行的線程初始化爲程序的初始線程。程序中其餘的線程都由他來啓動。Java中的線程分爲兩種:守護線程 (daemon)和普通線程(non-daemon)。守護線程是Java虛擬機本身使用的線程,好比負責垃圾收集的線程就是一個守護線程。固然,你也可 以把本身的程序設置爲守護線程。包含Main()方法的初始線程不是守護線程。
只要Java虛擬機中還有普通的線程在執行,Java虛擬機就不會中止。若是有足夠的權限,你能夠調用exit()方法終止程序。
3、Java虛擬機的體系結構
在Java虛擬機的規範中定義了一系列的子系統、內存區域、數據類型和使用指南。這些組件構成了Java虛擬機的內部結構,他們不只僅爲Java虛擬機的實現提供了清晰的內部結構,更是嚴格規定了Java虛擬機實現的外部行爲。
每個Java虛擬機都由一個類加載器子系統(class loader subsystem),負責加載程序中的類型(類和接口),並賦予惟一的名字。每個Java虛擬機都有一個執行引擎(execution engine)負責執行被加載類中包含的指令。
程序的執行須要必定的內存空間,如字節碼、被加載類的其餘額外信息、程序中的對象、方法的參數、返回值、本地變量、處理的中間變量等等。Java虛擬機將 這些信息通通保存在數據區(data areas)中。雖然每一個Java虛擬機的實現中都包含數據區,可是Java虛擬機規範對數據區的規定卻很是的抽象。許多結構上的細節部分都留給了 Java虛擬機實現者本身發揮。不一樣Java虛擬機實現上的內存結構千差萬別。一部分實現可能佔用不少內存,而其餘如下可能只佔用不多的內存;一些實現可 能會使用虛擬內存,而其餘的則不使用。這種比較精煉的Java虛擬機內存規約,可使得Java虛擬機能夠在普遍的平臺上被實現。
數據區中的一部分是整個程序共有,其餘部分被單獨的線程控制。每個Java虛擬機都包含方法區(method area)和堆(heap),他們都被整個程序共享。Java虛擬機加載並解析一個類之後,將從類文件中解析出來的信息保存與方法區中。程序執行時建立的 對象都保存在堆中。
當一個線程被建立時,會被分配只屬於他本身的PC寄存器「pc register」(程序計數器)和Java堆棧(Java stack)。當線程不掉用本地方法時,PC寄存器中保存線程執行的下一條指令。Java堆棧保存了一個線程調用方法時的狀態,包括本地變量、調用方法的 參數、返回值、處理的中間變量。調用本地方法時的狀態保存在本地方法堆棧中(native method stacks),可能再寄存器或者其餘非平臺獨立的內存中。
Java堆棧有堆棧塊(stack frames (or frames))組成。堆棧塊包含Java方法調用的狀態。當一個線程調用一個方法時,Java虛擬機會將一個新的塊壓到Java堆棧中,當這個方法運行結束時,Java虛擬機會將對應的塊彈出並拋棄。
Java虛擬機不使用寄存器保存計算的中間結果,而是用Java堆棧在存放中間結果。這是的Java虛擬機的指令更緊湊,也更容易在一個沒有寄存器的設備上實現Java虛擬機。
圖中的Java堆棧中向下增加的,PC寄存器中線程三爲灰色,是由於它正在執行本地方法,他的下一條執行指令不保存在PC寄存器中。
4、數據類型(Data Types)
全部Java虛擬機中使用的數據都有肯定的數據類型,數據類型和操做都在Java虛擬機規範中嚴格定義。Java中的數據類型分爲原始數據類型 (primitive types)和引用數據類型(reference type)。引用類型依賴於實際的對象,但不是對象自己。原始數據類型不依賴於任何東西,他們就是自己表示的數據。
全部Java程序語言中的原始 數據類型,都是Java虛擬機的原始數據類型,除了布爾型(boolean)。當編譯器將Java源代碼編譯爲本身碼時,使用整型(int)或者字節型 (byte)去表示布爾型。在Java虛擬機中使用整數0表示布爾型的false,使用非零整數表示布爾型的true,布爾數組被表示爲字節數組,雖然他 們可能會以字節數組或者字節塊(bit fields)保存在堆中。
除了布爾型,其餘Java語言中的原始類型都是Java虛擬機中的數據類型。在Java中數據類型被分爲:整形的byte,short,int,long;char和浮點型的float,double。Java語言中的數據類型在任何主機上都有一樣的範圍。
在Java虛擬機中還存在一個Java語言中不能使用的原始數據類型返回值類型(returnValue)。這種類型被用來實現Java程序中的「finally clauses」,具體的參見18章的「Finally Clauses」。
引用類型可能被建立爲:類類型(class type),接口類型(interface type),數組類型(array type)。他們都引用被動態建立的對象。當引用類型引用null時,說明沒有引用任何對象。
Java虛擬機規範只定義了每一種數據類型表示的範圍,沒有定義在存儲時每種類型佔用的空間。他們如何存儲由Java虛擬機的實現者本身決定。關於浮點型更多信息參見14章「Floating Point Arithmetic」。java
TypeRange
byte8-bit signed two's complement integer (-27 to 27 - 1, inclusive)
short16-bit signed two's complement integer (-215 to 215 - 1, inclusive)
int32-bit signed two's complement integer (-231 to 231 - 1, inclusive)
long64-bit signed two's complement integer (-263 to 263 - 1, inclusive)
char16-bit unsigned Unicode character (0 to 216 - 1, inclusive)
float32-bit IEEE 754 single-precision float
double64-bit IEEE 754 double-precision float
returnValueaddress of an opcode within the same method
referencereference to an object on the heap, or null
1)、類型的常量池(The constant pool for the type)
常量池中保存中全部類型是用的有序的常量集合,包含直接常量(literals)如字符串、整數、浮點數的常量,和對類型、字段、方法的符號引用。常量池 中每個保存的常量都有一個索引,就像數組中的字段同樣。由於常量池中保存中全部類型使用到的類型、字段、方法的字符引用,因此它也是動態鏈接的主要對 象。詳細信息參見第六章「The Java Class File」。
2)、類型字段的信息(Field information)
字段名、字段類型、字段的修飾符(public,private,protected,static,final,volatile,transient等)、字段在類中定義的順序。
3)、類型方法的信息(Method information)
方法名、方法的返回值類型(或者是void)、方法參數的個數、類型和他們的順序、字段的修飾符(public,private,protected,static,final,volatile,transient等)、方法在類中定義的順序
若是不是抽象和本地本法還須要保存
方法的字節碼、方法的操做數堆棧的大小和本地變量區的大小(稍候有詳細信息)、異常列表(詳細信息參見第十七章「Exceptions」。)
4)、類(靜態)變量(Class Variables)
類變量被全部類的實例共享,即便不經過類的實例也能夠訪問。這些變量綁定在類上(而不是類的實例上),因此他們是類的邏輯數據的一部分。在Java虛擬機使用這個類以前就須要爲類變量(non-final)分配內存
常量(final)的處理方式於這種類變量(non-final)不同。每個類型在用到一個常量的時候,都會複製一份到本身的常量池中。常量也像類變 量同樣保存在方法區中,只不過他保存在常量池中。(多是,類變量被全部實例共享,而常量池是每一個實例獨有的)。Non-final類變量保存爲定義他的 類型數據(data for the type that declares them)的一部分,而final常量保存爲使用他的類型數據(data for any type that uses them)的一部分。詳情參見第六章「The Java Class FileThe Java Class File」
5)、指向類加載器的引用(A reference to class ClassLoader)
每個被Java虛擬機加載的類型,虛擬機必須保存這個類型是否由原始類加載器或者類加載器加載。那些被類加載器加載的類型必須保存一個指向類加載器的引 用。當類加載器動態鏈接時,會使用這條信息。當一個類引用另外一個類時,虛擬機必須保存那個被引用的類型是被同一個類加載器加載的,這也是虛擬機維護不一樣命 名空間的過程。詳情參見第八章「The Linking Model」
6)、指向Class類的引用(A reference to class Class)
Java虛擬機爲每個加載的類型建立一個java.lang.Class類的實例。你也能夠經過Class類的方法:
public static Class forName(String className)來查找或者加載一個類,並取得相應的Class類的實例。經過這個Class類的實例,咱們能夠訪問Java虛擬機方法區中的信息。具體參照Class類的JavaDoc。
二、方法列表(Method Tables)
爲了更有效的訪問全部保存在方法區中的數據,這些數據的存儲結構必須通過仔細的設計。全部方法區中,除了保存了上邊的那些原始信息外,還有一個爲了加快存 取速度而設計的數據結構,好比方法列表。每個被加載的非抽象類,Java虛擬機都會爲他們產生一個方法列表,這個列表中保存了這個類可能調用的全部實例 方法的引用,報錯那些父類中調用的方法。詳情參見第八章「The Linking Model」數組
8、堆
當Java程序建立一個類的實例或者數組時,都在堆中爲新的對象分配內存。虛擬機中只有一個堆,全部的線程都共享他。
一、垃圾收集(Garbage Collection)
垃圾收集是釋放沒有被引用的對象的主要方法。它也可能會爲了減小堆的碎片,而移動對象。在Java虛擬機的規範中沒有嚴格定義垃圾收集,只是定義一個Java虛擬機的實現必須經過某種方式管理本身的堆。詳情參見第九章「Garbage Collection」。
二、對象存儲結構(Object Representation)
Java虛擬機的規範中沒有定義對象怎樣在堆中存儲。每個對象主要存儲的是他的類和父類中定義的對象變量。對於給定的對象的引用,虛擬機必須嫩耨很快的 定位到這個對象的數據。另爲,必須提供一種經過對象的引用方法對象數據的方法,好比方法區中的對象的引用,因此一個對象保存的數據中每每含有一個某種形式 指向方法區的指針。
一個可能的堆的設計是將堆分爲兩個部分:引用池和對象池。一個對象的引用就是指向引用池的本地指針。每個引用池中的條目都包含兩個部分:指向對象池中對 象數據的指針和方法區中對象類數據的指針。這種設計可以方便Java虛擬機堆碎片的整理。當虛擬機在對象池中移動一個對象的時候,只須要修改對應引用池中 的指針地址。可是每次訪問對象的數據都須要處理兩次指針。下圖演示了這種堆的設計。在第九章的「垃圾收集」中的HeapOfFish Applet演示了這種設計。
另外一種堆的設計是:一個對象的引用就是一個指向一堆數據和指向相應對象的偏移指針。這種設計方便了對象的訪問,但是對象的移動要變的異常複雜。下圖演示了這種設計
當程序試圖將一個對象轉換爲另外一種類型時,虛擬機須要判斷這種轉換是不是這個對象的類型,或者是他的父類型。當程序適用instanceof語句的時候也 會作相似的事情。當程序調用一個對象的方法時,虛擬機須要進行動態綁定,他必須判斷調用哪個類型的方法。這也須要作上面的判斷。
不管虛擬機實現者使用哪種設計,他均可能爲每個對象保存一個相似方法列表的信息。由於他能夠提高對象方法調用的速度,對提高虛擬機的性能很是重要,但 是虛擬機的規範中比沒有要求必須實現相似的數據結構。下圖描述了這種結構。圖中顯示了一個對象引用相關聯的全部的數據結構,包括:
1)、一個指向類型數據的指針
2)、一個對象的方法列表。方法列表是一個指向全部可能被調用對象方法的指針數組。方法數據包括三個部分:操做碼堆棧的大小和方法堆棧的本地變量區;方法的字節碼;異常列表。
每個Java虛擬機中的對象必須關聯一個用於同步多線程的lock(mutex)。同一時刻,只能有一個對象擁有這個對象的鎖。當一個擁有這個這個對象 的鎖,他就能夠屢次申請這個鎖,可是也必須釋放相應次數的鎖才能真正釋放這個對象鎖。不少對象在整個生命週期中都不會被鎖,因此這個信息只有在須要時才需 要添加。不少Java虛擬機的實現都沒有在對象的數據中包含「鎖定數據」,只是在須要時才生成相應的數據。除了實現對象的鎖定,每個對象還邏輯關聯到一 個「wait set」的實現。鎖定幫組線程獨立處理共享的數據,不須要妨礙其餘的線程。「wait set」幫組線程協做完成同一個目標。「wait set」每每經過Object類的wait()和notify()方法來實現。
垃圾收集也須要堆中的對象是否被關聯的信息。Java虛擬機規範中指出垃圾收集一個運行一個對象的finalizer方法一次,可是允許 finalizer方法從新引用這個對象,當這個對象再次不被引用時,就不須要再次調用finalize方法。因此虛擬機也須要保存finalize方法 是否運行過的信息。更多信息參見第九章的「垃圾收集」
三、數組的保存(Array Representation)
在Java 中,數組是一種徹底意義上的對象,他和對象同樣保存在堆中、有一個指向Class類實例的引用。全部同一維度和類型的數組擁有一樣的Class,數組的長 度不作考慮。對應Class的名字表示爲維度和類型。好比一個整型數據的Class爲「[I」,字節型三維數組Class名爲「[[[B」,兩維對象數據 Class名爲「[[Ljava.lang.Object」。
多維數組被表示爲數組的數組,以下圖:
數組必須在堆中保存數組的長度,數組的數據和一些對象數組類型數據的引用。經過一個數組引用的,虛擬機應該可以取得一個數組的長度,經過索引可以訪問特定 的數據,可以調用Object定義的方法。Object是全部數據類的直接父類。更多信息參見第六章「類文件」。
9、PC寄存器(程序計數器)(The Program Counter)
每個線程開始執行時都會被建立一個程序計數器。程序計數器只有一個字長(word),因此它可以保存一個本地指針和returnValue。當線程執行 時,程序計數器中存放了正在執行指令的地址,這個地址可使一個本地指針,也可使一個從方法字節碼開始的偏移指針。若是執行本地方法,程序計數器的值沒 有被定義。
10、Java堆棧(The Java Stack)
當一個線程啓動時,Java虛擬機會爲他建立一個Java堆棧。Java堆棧用一些離散的frame類紀錄線程的狀態。Java虛擬機堆Java堆棧的操做只有兩種:壓入和彈出frames。
線程中正在執行的方法被稱爲當前方法(current method),當前方法所對應的frame被稱爲當前幀(current frame)。定義當前方法的類被稱爲當前類(current class),當前類的常量池被稱爲當前常量池(current constant pool.)。當線程執行時,Java虛擬機會跟蹤當前類和當前常量池。但線程操做保存在幀中的數據時,他只操做當前幀的數據。
當線程調用一個方法時,虛擬機會生成一個新的幀,並壓入線程的Java堆棧。這個新的幀變成當前幀。當方法執行時,他使用當前幀保存方法的參數、本地變 量、中間結構和其餘數據。方法有兩種退出方式:正常退出和異常推出。不管方法以哪種方式推出,Java虛擬機都會彈出並丟棄方法的幀,上一個方法的幀變 爲當前幀。
全部保存在幀中的數據都只能被擁有它的線程訪問,線程不能訪問其餘線程的堆棧中的數據。因此,訪問方法的本地變量時,不須要考慮多線程同步。
和方法區、堆同樣,Java堆棧不須要連續的內存空間,它能夠被保存在一個分散的內存空間或者堆上。堆棧具體的數據和長度都有Java虛擬機的實現者本身定義。一些實現可能提供了執行堆棧最大值和最小值的方法。
11、堆棧幀(The Stack Frame)
堆棧幀包含三部分:本地變量、操做數堆棧和幀數據。本地變量和操做數堆棧的大小都是一字(word)爲單位的,他們在編譯就已經肯定。幀數據的大小取決於 不一樣的實現。當程序調用一個方法時,虛擬機從類數據中取得本地變量和操做數堆棧的大小,建立一個合適大小和幀,而後壓入Java堆棧中。
一、本地變量(Local Variables)
本地變量在Java堆棧幀中被組織爲一個從0計數的數組,指令經過提供他們的索引從本地變量區中取得相應的值。Int,float,reference, returnValue佔一個字,byte,short,char被轉換成int而後存儲,long和doubel佔兩個字。
指令經過提供兩個字索引中的前一個來取得long,doubel的值。好比一個long的值存儲在索引3,4上,指令就能夠經過3來取得這個long類型的值。
本地變量區中包含了方法的參數和本地變量。編譯器將方法的參數以他們申明的順序放在數組的前面。可是編譯器卻能夠將本地變量任意排列在本地變量數組中,甚至兩個本地變量能夠公用一個地址,好比,當兩個本地變量在兩個不交疊的區域內,就像循環變量i,j。
虛擬機的實現者可使用任何結構來描述本地變量區中的數據,虛擬機規範中沒有定義如何存儲long和doubel。
二、操做數堆棧(Operand Stack)
向本地變量同樣,操做數堆棧也被組織爲一個以字爲單位的數組。可是不像本地變量那樣經過索引訪問,而是經過push和pop值來實現訪問的。若是一個指令push一個值到堆棧中,那麼下一個指令就能夠pop而且使用這個值。
操做數堆棧不像程序計數器那樣不能夠被指令直接訪問,指令能夠直接訪問操做數堆棧。Java虛擬機是一個以堆棧爲基礎,而不是以寄存器爲基礎的,由於它的 指令從堆棧中取得操做數,而不是同寄存器中。固然,指令也能夠從其餘地方去的操做數,好比指令後面的操做碼,或者常量池。可是Java虛擬機指令主要是從 操做數堆棧中取得他們須要的操做數。
Java虛擬機將操做數堆棧視爲工做區,不少指令經過先從操做數堆棧中pop值,在處理完之後再將結果push回操做數堆棧。一個add的指令執行過程如 下圖所示:先執行iload_0和iload_1兩條指令將須要相加的兩個數,從本地方法區中取出,並push到操做數堆棧中;而後執行iadd指令,現 pop出兩個值,相加,並將結果pusp進操做數堆棧中;最後執行istore_2指令,pop出結果,賦值到本地方法區中。
三、幀數據(Frame Data)
處理本地變量和操做數堆棧之外,java堆棧幀還包括了爲了支持常量池,方法返回值和異常分發須要的數據,他們被保存在幀數據中。
當虛擬機遇到使用指向常量池引用的指令時,就會經過幀數據中指向常量區的指針來訪問所須要的信息。前面提到過,常量區中的引用在最開始時都是符號引用。即便當虛擬機檢查這些引用時,他們也是字符引用。因此虛擬機須要在這時轉換這個引用。
當一個方法正常返回時,虛擬機須要重建那個調用這個方法的方法的堆棧幀。若是執行完的方法有返回值,虛擬機就須要將這個值push進調用方法的哪一個操做數堆棧中。
幀數據中也包含虛擬機用來處理異常的異常表的引用。異常表定義了一個被catch語句保護的一段字節碼。每個異常表中的個體又包含了須要保護的字節瑪的 範圍,和異常被捕捉到時須要執行的字節碼的位置。當一個方法拋出一個異常時,Java虛擬機就是用異常表去判斷如何處理這個異常。若是虛擬機找到了一個匹 配的catch,他就會將控制權交給catch語句。若是沒有找到匹配的catch,方法就會異常返回,而後再調用的方法中繼續這個過程。
除了以上的三個用途外,幀數據還可能包含一些依賴於實現的數據,好比調試的信息。
12、本地方法堆棧
本地方法區依賴於虛擬機的不一樣實現。虛擬機的實現者能夠本身決定使用哪種機制去執行本地方法。
任何本地方法接口(Native Method Interface)都使用某種形式的本地方法堆棧。
十3、執行引擎
一個java虛擬機實現的核心就是執行引擎。在Java虛擬機規範,執行引擎被描述爲一系列的指令。對於每個指令,規範都描述了他們應該作什麼,可是沒有說要如何去作。
一、指令集
在Java虛擬機中一個方法的字節碼流就是一個指令的序列。每個指令由一個字節的操做碼(Opcode)和可能存在的操做數(Operands)。操做 碼指示去作什麼,操做數提供一些執行這個操做碼可能須要的額外的信息。一個抽象的執行引擎每次執行一個指令。這個過程發生在每個執行的線程中。
有時,執行引擎可能會遇到一個須要調用本地方法的指令,在這種狀況下,執行引擎會去試圖調用本地方法,但本地方法返回時,執行引擎會繼續執行字節碼流中的下一個指令。本地方法也能夠當作對Java虛擬機中的指令集的一種擴充。
決定下一步執行那一條指令也是執行引擎工做的一部分。執行引擎有三種方法去取得下一條指令。多數指令會執行跟在他會面的指令;一些像goto, return的指令,會在他們執行的時候決定他們的下一條指令;當一個指令拋出異常時,執行引擎經過匹配catch語句來決定下一條應該執行的指令。
平臺獨立性、網絡移動性、安全性左右了Java虛擬機指令集的設計。平臺獨立性是指令集設計的主要影響因素之一。基於堆棧的結構使得Java虛擬機能夠在 更多的平臺上實現。更小的操做碼,緊湊的結構使得字節碼能夠更有效的利用網絡帶寬。一次性的字節碼驗證,使得字節碼更安全,而不影響太多的性能。
二、執行技術
許多種執行技術能夠用在Java虛擬機的實現中:解釋執行,及時編譯(just-in-time compiling),hot-spot compiling,native execution in silicon。
三、線程
Java虛擬機規範定義了一種爲了在更多平臺上實現的線程模型。Java線程模型的一個目標時能夠利用本地線程。利用本地線程可讓Java程序中的線程能過在多處理器機器上真正的同時執行。
Java線程模型的一個代價就是線程優先級,一個Java線程能夠在1-10的優先級上運行。1最低,10最高。若是設計者使用了本地線程,他們可能將這 10個優先級映射到本地優先級上。Java虛擬機規範只定義了,高一點優先級的線程能夠卻一些cpu時間,低優先級的線程在全部高優先級線程都堵塞時,也 能夠獲取一些cpu時間,可是這沒有保證:低優先級的線程在高優先級線程沒有堵塞時不能夠得到必定的cpu時間。所以,若是須要在不一樣的線程間協做,你必 須使用的「同步(synchronizatoin)」。
同步意味着兩個部分:對象鎖(object locking)和線程等待、激活(thread wait and notify)。對象鎖幫助線程能夠不受其餘線程的干擾。線程等待、激活可讓不一樣的線程進行協做。
在Java虛擬機的規範中,Java線程被描述爲變量、主內存、工做內存。每個Java虛擬機的實例都有一個主內存,他包含了全部程序的變量:對象、數組合類變量。每個線程都有本身的工做內存,他保存了哪些他可能用到的變量的拷貝。規則:
1)、從主內存拷貝變量的值到工做內存中
2)、將工做內存中的值寫會主內存中
若是一個變量沒有被同步化,線程可能以任何順序更新主內存中的變量。爲了保證多線程程序的正確的執行,必須使用同步機制。
十4、本地方法接口(Native Method Interface)
Java虛擬機的實現並非必須實現本地方法接口。一些實現可能根本不支持本地方法接口。Sun的本地方法接口是JNI(Java Native Interface)。
十5、現實中的機器(The Real Machine)
十6、數學方法:仿真(Eternal Math : A Simulation)安全