1. 程式人生 > >虛擬機器生命週期

虛擬機器生命週期

轉載自:https://www.cnblogs.com/like-minded/p/5157667.html
一個執行時的Java虛擬機器例項的天職是:負責執行一個java程式。當啟動一個Java程式時,一個虛擬機器例項也就誕生了。當該程式關閉退出,這個虛擬機器例項也就隨之消亡。如果同一臺計算機上同時執行三個Java程式,將得到三個Java虛擬機器例項。每個Java程式都運行於它自己的Java虛擬機器例項中。

Java虛擬機器例項通過呼叫某個初始類的main()方法來執行一個Java程式。而這個main()方法必須是共有的(public)、靜態的(static)、返回值為void,並且接受一個字串陣列作為引數。任何擁有這樣一個main()方法的類都可以作為Java程式執行的起點。

public class Test {
 
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println("Hello World");
    }
}

在上面的例子中,Java程式初始類中的main()方法,將作為該程式初始執行緒的起點,任何其他的執行緒都是由這個初始執行緒啟動的。
在Java虛擬機器內部有兩種執行緒:守護執行緒和非守護執行緒。守護執行緒通常是由虛擬機器自己使用的,比如執行垃圾收集任務的執行緒。但是,Java程式也可以把它建立的任何執行緒標記為守護執行緒。而Java程式中的初始執行緒——就是開始於main()的那個,是非守護執行緒。
只要還有任何非守護執行緒在執行,那麼這個Java程式也在繼續執行。當該程式中所有的非守護執行緒都終止時,虛擬機器例項將自動退出。假若安全管理器允許,程式本身也能夠通過呼叫Runtime類或者System類的exit()方法來退出。

JAVA虛擬機器的體系結構

下圖是JAVA虛擬機器的結構圖,每個Java虛擬機器都有一個類裝載子系統,它根據給定的全限定名來裝入型別(類或介面)。同樣,每個Java虛擬機器都有一個執行引擎,它負責執行那些包含在被裝載類的方法中的指令。

當JAVA虛擬機器執行一個程式時,它需要記憶體來儲存許多東西,例如:位元組碼、從已裝載的class檔案中得到的其他資訊、程式建立的物件、傳遞給方法的引數,返回值、區域性變數等等。Java虛擬機器把這些東西都組織到幾個“執行時資料區”中,以便於管理。
某些執行時資料區是由程式中所有執行緒共享的,還有一些則只能由一個執行緒擁有。每個Java虛擬機器例項都有一個方法區以及一個堆,它們是由該虛擬機器例項中所有的執行緒共享的。當虛擬機器裝載一個class檔案時,它會從這個class檔案包含的二進位制資料中解析型別資訊。然後把這些型別資訊放到方法區中。當程式執行時,虛擬機器會把所有該程式在執行時建立的物件都放到堆中。

當每一個新執行緒被建立時,它都將得到它自己的PC暫存器(程式計數器)以及一個Java棧,如果執行緒正在執行的是一個Java方法(非本地方法),那麼PC暫存器的值將總是指向下一條將被執行的指令,而它的Java棧則總是儲存該執行緒中Java方法呼叫的狀態——包括它的區域性變數,被呼叫時傳進來的引數、返回值,以及運算的中間結果等等。而本地方法呼叫的狀態,則是以某種依賴於具體實現的方法儲存在本地方法棧中,也可能是在暫存器或者其他某些與特定實現相關的記憶體區中。
Java棧是由許多棧幀(stack frame)組成的,一個棧幀包含一個Java方法呼叫的狀態。當執行緒呼叫一個Java方法時,虛擬機器壓入一個新的棧幀到該執行緒的Java棧中,當該方法返回時,這個棧幀被從Java棧中彈出並拋棄。
Java虛擬機器沒有暫存器,其指令集使用Java棧來儲存中間資料。這樣設計的原因是為了保持Java虛擬機器的指令集儘量緊湊、同時也便於Java虛擬機器在那些只有很少通用暫存器的平臺上實現。另外,Java虛擬機器這種基於棧的體系結構,也有助於執行時某些虛擬機器實現的動態編譯器和即時編譯器的程式碼優化。
下圖描繪了Java虛擬機器為每一個執行緒建立的記憶體區,這些記憶體區域是私有的,任何執行緒都不能訪問另一個執行緒的PC暫存器或者Java棧。

上圖展示了一個虛擬機器例項的快照,它有三個執行緒正在執行。執行緒1和執行緒2都正在執行Java方法,而執行緒3則正在執行一個本地方法。
Java棧都是向下生長的,而棧頂都顯示在圖的底部。當前正在執行的方法的棧幀則以淺色表示,對於一個正在執行Java方法的執行緒而言,它的PC暫存器總是指向下一條將被執行的指令。比如執行緒1和執行緒2都是以淺色顯示的,由於執行緒3當前正在執行一個本地方法,因此,它的PC暫存器——以深色顯示的那個,其值是不確定的。

資料型別

Java虛擬機器是通過某些資料型別來執行計算的,資料型別可以分為兩種:基本型別和引用型別,基本型別的變數持有原始值,而引用型別的變數持有引用值。

Java語言中的所有基本型別同樣也都是Java虛擬機器中的基本型別。但是boolean有點特別,雖然Java虛擬機器也把boolean看做基本型別,但是指令集對boolean只有很有限的支援,當編譯器把Java原始碼編譯為位元組碼時,它會用int或者byte來表示boolean。在Java虛擬機器中,false是由整數零來表示的,所有非零整數都表示true,涉及boolean值的操作則會使用int。另外,boolean陣列是當做byte陣列來訪問的,但是在“堆”區,它也可以被表示為位域。
Java虛擬機器還有一個只在內部使用的基本型別:returnAddress,Java程式設計師不能使用這個型別,這個基本型別被用來實現Java程式中的finally子句。該型別是jsr, ret以及jsr_w指令需要使用到的,它的值是JVM指令的操作碼的指標。returnAddress型別不是簡單意義上的數值,不屬於任何一種基本型別,並且它的值是不能被執行中的程式所修改的。
Java虛擬機器的引用型別被統稱為“引用(reference)”,有三種引用型別:類型別、介面型別、以及陣列型別,它們的值都是對動態建立物件的引用。類型別的值是對類例項的引用;陣列型別的值是對陣列物件的引用,在Java虛擬機器中,陣列是個真正的物件;而介面型別的值,則是對實現了該介面的某個類例項的引用。還有一種特殊的引用值是null,它表示該引用變數沒有引用任何物件。

類裝載子系統

在JAVA虛擬機器中,負責查詢並裝載型別的那部分被稱為類裝載子系統。
JAVA虛擬機器有兩種類裝載器:啟動類裝載器和使用者自定義類裝載器。前者是JAVA虛擬機器實現的一部分,後者則是Java程式的一部分。由不同的類裝載器裝載的類將被放在虛擬機器內部的不同名稱空間中。
類裝載器子系統涉及Java虛擬機器的其他幾個組成部分,以及幾個來自java.lang庫的類。比如,使用者自定義的類裝載器是普通的Java物件,它的類必須派生自java.lang.ClassLoader類。ClassLoader中定義的方法為程式提供了訪問類裝載器機制的介面。此外,對於每一個被裝載的型別,JAVA虛擬機器都會為它建立一個java.lang.Class類的例項來代表該型別。和所有其他物件一樣,使用者自定義的類裝載器以及Class類的例項都放在記憶體中的堆區,而裝載的型別資訊則都位於方法區。
類裝載器子系統除了要定位和匯入二進位制class檔案外,還必須負責驗證被匯入類的正確性,為類變數分配並初始化記憶體,以及幫助解析符號引用。這些動作必須嚴格按以下順序進行:
(1)裝載——查詢並裝載型別的二進位制資料。
(2)連線——指向驗證、準備、以及解析(可選)。
● 驗證  確保被匯入型別的正確性。
● 準備  為類變數分配記憶體,並將其初始化為預設值。
● 解析  把型別中的符號引用轉換為直接引用。
(3)初始化——把類變數初始化為正確初始值。
每個JAVA虛擬機器實現都必須有一個啟動類裝載器,它知道怎麼裝載受信任的類。
每個類裝載器都有自己的名稱空間,其中維護著由它裝載的型別。所以一個Java程式可以多次裝載具有同一個全限定名的多個型別。這樣一個型別的全限定名就不足以確定在一個Java虛擬機器中的唯一性。因此,當多個類裝載器都裝載了同名的型別時,為了惟一地標識該型別,還要在型別名稱前加上裝載該型別(指出它所位於的名稱空間)的類裝載器標識。

方法區

在Java虛擬機器中,關於被裝載型別的資訊儲存在一個邏輯上被稱為方法區的記憶體中。當虛擬機器裝載某個型別時,它使用類裝載器定位相應的class檔案,然後讀入這個class檔案——1個線性二進位制資料流,然後它傳輸到虛擬機器中,緊接著虛擬機器提取其中的型別資訊,並將這些資訊儲存到方法區。該型別中的類(靜態)變數同樣也是儲存在方法區中。
JAVA虛擬機器在內部如何儲存型別資訊,這是由具體實現的設計者來決定的。
當虛擬機器執行Java程式時,它會查詢使用儲存在方法區中的型別資訊。由於所有執行緒都共享方法區,因此它們對方法區資料的訪問必須被設計為是執行緒安全的。比如,假設同時有兩個執行緒都企圖訪問一個名為Lava的類,而這個類還沒有被裝入虛擬機器,那麼,這時只應該有一個執行緒去裝載它,而另一個執行緒則只能等待。
對於每個裝載的型別,虛擬機器都會在方法區中儲存以下型別資訊:
  ● 這個型別的全限定名
  ● 這個型別的直接超類的全限定名(除非這個型別是java.lang.Object,它沒有超類)
  ● 這個型別是類型別還是介面型別
  ● 這個型別的訪問修飾符(public、abstract或final的某個子集)
  ● 任何直接超介面的全限定名的有序列表
除了上面列出的基本型別資訊外,虛擬機器還得為每個被裝載的型別儲存以下資訊:
  ● 該型別的常量池
  ● 欄位資訊
  ● 方法資訊
  ● 除了常量以外的所有類(靜態)變數
  ● 一個到類ClassLoader的引用
  ● 一個到Class類的引用