標籤:
jvm=》 java虛擬機器
一、java虛擬機器的生命週期:
Java虛擬機器的生命週期 一個運行中的Java虛擬機器有著一個清晰的任務:執行Java程式。程式開始執行時他才運行,程式結束時他就停止。你在同一台機器上運行三個程式,就會有三個運行中的Java虛擬機器。 Java虛擬機器總是開始於一個main()方法,這個方法必須是公有、返回void、直接受一個字串數組。在程式執行時,你必須給Java虛擬機器指明這個包換main()方法的類名。 Main()方法是程式的起點,他被執行的線程初始化為程式的初始線程。程式中其他的線程都由他來啟動。
Java中的線程分為兩種:守護線程 (daemon)和普通線程(non-daemon)。
守護線程是Java虛擬機器自己使用的線程,比如負責垃圾收集的線程就是一個守護線程。當然,你也可 以把自己的程式設定為守護線程。包含Main()方法的初始線程不是守護線程。 只要Java虛擬機器中還有普通的線程在執行,Java虛擬機器就不會停止。如果有足夠的許可權,你可以調用exit()方法終止程式。
二、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虛擬機器中的類載入器分為兩種:原始類載入器(primordial class loader)和類載入器對象(class loader objects)。
原始類載入器是Java虛擬機器實現的一部分。類載入器對象是運行中的程式的一部分。不同類載入器載入的類被不同的命名空間所分割。
類載入器調用了許多Java虛擬機器中其他的部分和java.lang包中的很多類。比如,類載入對象就是java.lang.ClassLoader子類 的執行個體,ClassLoader類中的方法可以訪問虛擬機器中的類載入機制;每一個被Java虛擬機器載入的類都會被表示為一個 java.lang.Class類的執行個體。像其他對象一樣,類載入器對象和Class對象都儲存在堆中,被載入的資訊被儲存在方法區中。
1、載入、串連、初始化(Loading, Linking and Initialization)類載入子系統不僅僅負責定位並載入類檔案,他按照以下嚴格的步驟作了很多其他的事情:
1)、載入:尋找並匯入指定類型(類和介面)的二進位資訊2)、串連:進行驗證、準備和解析 ①驗證:確保匯入類型的正確性 ②準備:為類型分配記憶體並初始化為預設值 ③解析:將字元引用解析為直接飲用3)、初始化:調用Java代碼,初始化類變數為合適的值
2、原始類載入器(The Primordial Class Loader) 每個Java虛擬機器都必須實現一個原始類載入器,他能夠載入那些遵守類檔案格式並且被信任的類。但是,Java虛擬機器的規範並沒有定義如何載入類,這由 Java虛擬機器實現者自己決定。對於給定類型名的類型,原始萊載入器必須找到那個類型名加“.class”的檔案並載入入虛擬機器中。
3、類載入器對象 雖然類載入器對象是Java程式的一部分,但是ClassLoader類中的三個方法可以訪問Java虛擬機器中的類載入子系統。
1)、protected final Class defineClass(…):使用這個方法可以出入一個位元組數組,定義一個新的類型。 2)、protected Class findSystemClass(String name):載入指定的類,如果已經載入,就直接返回。 3)、protected final void resolveClass(Class c):defineClass()方法只是載入一個類,這個方法負責後續的動態串連和初始化。
4、命名空間 當多個類載入器載入了同一個類時,為了保證他們名字的唯一性,需要在類名前加上載入該類的類載入器的標識。
四、方法區:
在Java虛擬機器中,被載入類型的資訊都儲存在方法區中。這寫資訊在記憶體中的組織形式由虛擬機器的實現者定義,比如,虛擬機器工作在一個“little- endian”的處理器上,他就可以將資訊儲存為“little-endian”格式的,雖然在Java類檔案中他們是以“big-endian”格式保 存的。設計者可以用最適合并地機器的表示格式來儲存資料,以保證程式能夠以最快的速度執行。但是,在一個只有很小記憶體的裝置上,虛擬機器的實現者就不會佔用 很大的記憶體。 程式中的所有線程共用一個方法區,所以存取方法區資訊的方法必須是安全執行緒的。如果你有兩個線程都去載入一個叫Lava的類,那隻能由一個線程被容許去載入這個類,另一個必須等待。 在程式運行時,方法區的大小是可變的,程式在運行時可以擴充。有些Java虛擬機器的實現也可以通過參數也訂製方法區的初始大小,最小值和最大值。 方法區也可以被垃圾收集。因為程式中的內由類載入器動態載入,所有類可能變成沒有被引用(unreferenced)的狀態。當類變成這種狀態時,他就可 能被垃圾收集掉。沒有載入的類包括兩種狀態,一種是真正的沒有載入,另一個種是“unreferenced”的狀態。詳細資料參見第七章的類的生命週期 (The Lifetime of a Class)。
五、堆:
當Java程式建立一個類的執行個體或者數組時,都在堆中為新的對象分配記憶體。虛擬機器中只有一個堆,所有的線程都共用他。
1、垃圾收集(Garbage Collection) 垃圾收集是釋放沒有被引用的對象的主要方法。它也可能會為了減少堆的片段,而移動對象。在Java虛擬機器的規範中沒有嚴格定義垃圾收集,只是定義一個Java虛擬機器的實現必須通過某種方式管理自己的堆。
2、Object Storage Service結構(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方法 是否運行過的資訊。更多資訊參見第九章的“垃圾收集”
3、數組的儲存(Array Representation)在Java 中,數組是一種完全意義上的對象,他和對象一樣儲存在堆中、有一個指向Class類執行個體的引用。所有同一維度和類型的數組擁有同樣的Class,數組的長 度不做考慮。對應Class的名字表示為維度和類型。比如一個整型資料的Class為“[I”,位元組型三維數組Class名為“[[[B”,兩維對象資料 Class名為“[[Ljava.lang.Object”。 數組必須在堆中儲存數組的長度,數組的資料和一些對象數群組類型資料的引用。通過一個數組引用的,虛擬機器應該能夠取得一個數組的長度,通過索引能夠訪問特定 的資料,能夠調用Object定義的方法。Object是所有資料類的直接父類。
JVM自身的物理結構
此圖看出jvm記憶體結構
JVM記憶體結構主要包括兩個子系統和兩個組件。兩個子系統分別是Classloader子系統和Executionengine(執行引擎)子系統;兩個組件分別是Runtimedataarea(運行時資料區域)組件和Nativeinterface(本地介面)組件。
Classloader子系統的作用:
根據給定的全限定名類名(如java.lang.Object)來裝載class檔案的內容到Runtimedataarea中的methodarea(方法地區)。Java程式員可以extendsjava.lang.ClassLoader類來寫自己的Classloader。
Executionengine子系統的作用:
執行classes中的指令。任何JVMspecification實現(JDK)的核心都是Executionengine,不同的JDK例如Sun的JDK和IBM的JDK好壞主要就取決於他們各自實現的Executionengine的好壞。
Nativeinterface組件:
與nativelibraries互動,是其它程式設計語言互動的介面。當調用native方法的時候,就進入了一個全新的並且不再受虛擬機器限制的世界,所以也很容易出現JVM無法控制的nativeheapOutOfMemory。
RuntimeDataArea組件:
這就是我們常說的JVM的記憶體了。它主要分為五個部分——
1、Heap(堆):一個Java虛擬執行個體中只存在一個堆空間
2、MethodArea(方法地區):被裝載的class的資訊儲存在Methodarea的記憶體中。當虛擬機器裝載某個類型時,它使用類裝載器定位相應的class檔案,然後讀入這個class檔案內容並把它傳輸到虛擬機器中。
3、JavaStack(java的棧):虛擬機器只會直接對Javastack執行兩種操作:以幀為單位的壓棧或出棧
4、ProgramCounter(程式計數器):每一個線程都有它自己的PC寄存器,也是該線程啟動時建立的。PC寄存器的內容總是指向下一條將被執行指令的餓地址,這裡的地址可以是一個本地指標,也可以是在方法區中相對應於該方法起始指令的位移量。
5、Nativemethodstack(本地方法棧):儲存native方法進入地區的地址
Java基礎-JVM