JAVA虛擬機體系結構

1 Java虛擬機的生命週期

一個運行時的Java虛擬機實例的天職是:負責運行一個java程序。當啓動一個Java程序時,一個虛擬機實例也就誕生了。當該程序關閉退出,這個虛擬機實例也就隨之消亡。若是同一臺計算機上同時運行三個Java程序,將獲得三個Java虛擬機實例。每一個Java程序都運行於它本身的Java虛擬機實例中。html

Java虛擬機實例經過調用某個初始類的main()方法來運行一個Java程序。而這個main()方法必須是共有的(public)、靜態的(static)、返回值爲void,而且接受一個字符串數組做爲參數。任何擁有這樣一個main()方法的類均可以做爲Java程序運行的起點。java

public class Test {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

在上面的例子中,Java程序初始類中的main()方法,將做爲該程序初始線程的起點,任何其餘的線程都是由這個初始線程啓動的。程序員

  在Java虛擬機內部有兩種線程:守護線程和非守護線程。守護線程一般是由虛擬機本身使用的,好比執行垃圾收集任務的線程。可是,Java程序也能夠把它建立的任何線程標記爲守護線程。而Java程序中的初始線程——就是開始於main()的那個,是非守護線程。算法

  只要還有任何非守護線程在運行,那麼這個Java程序也在繼續運行。當該程序中全部的非守護線程都終止時,虛擬機實例將自動退出。倘若安全管理器容許,程序自己也可以經過調用Runtime類或者System類的exit()方法來退出。api

 

2 Java虛擬機的體系結構

2.1 運行時數據區總覽

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

99WBYAADxCsWgBAACA9gaiBQAAADwEogUAAAA8BKIFAAAAPASiBQAAADwEogUAAAA8BKIFAAAAPASiBQAAADwEogUAAAA8BKIFAAAAPASiBQAAADwEogUAAAA85P8D0TRq40Sop+UAAAAASUVORK5CYII=

 

當JAVA虛擬機運行一個程序時,它須要內存來存儲許多東西,例如:字節碼、從已裝載的class文件中獲得的其餘信息、程序建立的對象、傳遞給方法的參數,返回值、局部變量等等。Java虛擬機把這些東西都組織到幾個「運行時數據區」中,以便於管理。安全

  某些運行時數據區是由程序中全部線程共享的,還有一些則只能由一個線程擁有。每一個Java虛擬機實例都有一個方法區以及一個堆,它們是由該虛擬機實例中全部的線程共享的。當虛擬機裝載一個class文件時,它會從這個class文件包含的二進制數據中解析類型信息。而後把這些類型信息放到方法區中。當程序運行時,虛擬機會把全部該程序在運行時建立的對象都放到堆中。數據結構


wdKO5x7ImAFMgAAAABJRU5ErkJggg==

  當每個新線程被建立時,它都將獲得它本身的PC寄存器(程序計數器)以及一個Java棧,若是線程正在執行的是一個Java方法(非本地方法),那麼PC寄存器的值將老是指向下一條將被執行的指令,而它的Java棧則老是存儲該線程中Java方法調用的狀態——包括它的局部變量,被調用時傳進來的參數、返回值,以及運算的中間結果等等。而本地方法調用的狀態,則是以某種依賴於具體實現的方法存儲在本地方法棧中,也多是在寄存器或者其餘某些與特定實現相關的內存區中。多線程

  Java棧是由許多棧幀(stack frame)組成的,一個棧幀包含一個Java方法調用的狀態。當線程調用一個Java方法時,虛擬機壓入一個新的棧幀到該線程的Java棧中,當該方法返回時,這個棧幀被從Java棧中彈出並拋棄。數據結構和算法

  Java虛擬機沒有寄存器,其指令集使用Java棧來存儲中間數據。這樣設計的緣由是爲了保持Java虛擬機的指令集儘可能緊湊、同時也便於Java虛擬機在那些只有不多通用寄存器的平臺上實現。另外,Java虛擬機這種基於棧的體系結構,也有助於運行時某些虛擬機實現的動態編譯器和即時編譯器的代碼優化。

  下圖描繪了Java虛擬機爲每個線程建立的內存區,這些內存區域是私有的,任何線程都不能訪問另外一個線程的PC寄存器或者Java棧。

  AB8HMuKWugMAAAAASUVORK5CYII=

  上圖展現了一個虛擬機實例的快照,它有三個線程正在執行。線程1和線程2都正在執行Java方法,而線程3則正在執行一個本地方法。

  Java棧都是向下生長的,而棧頂都顯示在圖的底部。當前正在執行的方法的棧幀則以淺色表示,對於一個正在運行Java方法的線程而言,它的PC寄存器老是指向下一條將被執行的指令。好比線程1和線程2都是以淺色顯示的,因爲線程3當前正在執行一個本地方法,所以,它的PC寄存器——以深色顯示的那個,其值是不肯定的。

2.1.1 數據類型

Java虛擬機是經過某些數據類型來執行計算的,數據類型能夠分爲兩種:基本類型和引用類型,基本類型的變量持有原始值,而引用類型的變量持有引用值。

  wWuzoHPsy4WPQAAAABJRU5ErkJggg==

  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中參數的傳遞有兩種,分別是按值傳遞和按引用傳遞。說一下按引用傳遞。

  「當一個對象被看成參數傳遞到一個方法」,這就是所謂的按引用傳遞。

public class User {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
public class Test {
    public void set(User user){
        user.setName("hello world");
        user = new User();
        user.setName("change");
    }
    public static void main(String[] args) {
        
        Test test = new Test();
        User user = new User();
        test.set(user);
        System.out.println(user.getName());
    }
}

輸出是「hello world」,下面就讓咱們來分析一下如上代碼。

User user = new User();

是在堆中建立了一個對象,並在棧中建立了一個引用,此引用指向該對象,以下圖:

nLLsMAAB5gi4DALAFXQYAYAu6DADAFnQZAIAt6DIAAFv+H6Ik3xlN5G9bAAAAAElFTkSuQmCC

 

test.set(user);

是將引用user做爲參數傳遞到set方法,注意:這裏傳遞的並非引用自己,而是一個引用的拷貝。也就是說這時有兩個引用(引用和引用的拷貝)同時指向堆中的對象,以下圖:

PgAAAABJRU5ErkJggg==

 

user.setName("hello world");

在set()方法中,「user引用的拷貝」操做堆中的User對象,給name屬性設置字符串"hello world"。以下圖:


4gAAAABJRU5ErkJggg==

 

user = new User();

在set()方法中,又建立了一個User對象,並將「user引用的拷貝」指向這個在堆中新建立的對象,以下圖:

  UZT01wAAADegOYCAABzoLkAAMAcaC4AADAHmgsAAMyB5gIAAHOguQAAwBxoLgAAMAeaCwAAzIHmAgAAc6C5AADAnP8Hy1Q6vyFWArYAAAAASUVORK5CYII=

 

user.setName("change");

  在set()方法中,「user引用的拷貝」操做的是堆中新建立的User對象。

mAAAAAElFTkSuQmCC

 

 

  set()方法執行完畢,目光再回到mian()方法

System.out.println(user.getName());

  由於以前,"user引用的拷貝"已經將堆中的User對象的name屬性設置爲了"hello world",因此當main()方法中的user調用getName()時,打印的結果就是"hello world"。以下圖:

  JArATl4VSgPGwOhTzYbDbh8oSFhaE8EGvA75FTAd9oR38oRhUU8hgYGLhx40Y30t1948YNO78fyGgCv0dOBX6dbQu1PBAEQRDEBCgPBEEQxGJQHgiCIIjFoDwQBEEQi0F5IAiCIBaD8kAQBEEsBuWBIAiCWAzKA0EQBLEYlAeCIAhiMSgPBEEQxGJQHgiCIIjFoDwQBEEQi0F5IAiCIBaD8kAQBEEsBuWBIAiCWAzKA0EQBLEYlAeCIAhiMf8ftvgOhy91YB4AAAAASUVORK5CYII=

 

2.2 類裝載子系統

在JAVA虛擬機中,負責查找並裝載類型的那部分被稱爲類裝載子系統。

  JAVA虛擬機有兩種類裝載器:啓動類裝載器和用戶自定義類裝載器。前者是JAVA虛擬機實現的一部分,後者則是Java程序的一部分。由不一樣的類裝載器裝載的類將被放在虛擬機內部的不一樣命名空間中。

  類裝載器子系統涉及Java虛擬機的其餘幾個組成部分,以及幾個來自java.lang庫的類。好比,用戶自定義的類裝載器是普通的Java對象,它的類必須派生自java.lang.ClassLoader類。ClassLoader中定義的方法爲程序提供了訪問類裝載器機制的接口。此外,對於每個被裝載的類型,JAVA虛擬機都會爲它建立一個java.lang.Class類的實例來表明該類型。和全部其餘對象同樣,用戶自定義的類裝載器以及Class類的實例都放在內存中的堆區,而裝載的類型信息則都位於方法區。

  類裝載器子系統除了要定位和導入二進制class文件外,還必須負責驗證被導入類的正確性,爲類變量分配並初始化內存,以及幫助解析符號引用。這些動做必須嚴格按如下順序進行:

  (1)裝載——查找並裝載類型的二進制數據。

  (2)鏈接——指向驗證、準備、以及解析(可選)。

    驗證  確保被導入類型的正確性。

    準備  爲類變量分配內存,並將其初始化爲默認值。

    、解析  把類型中的符號引用轉換爲直接引用。

  (3)初始化——把類變量初始化爲正確初始值。

  每一個JAVA虛擬機實現都必須有一個啓動類裝載器,它知道怎麼裝載受信任的類。

  每一個類裝載器都有本身的命名空間,其中維護着由它裝載的類型。因此一個Java程序能夠屢次裝載具備同一個全限定名的多個類型。這樣一個類型的全限定名就不足以肯定在一個Java虛擬機中的惟一性。所以,當多個類裝載器都裝載了同名的類型時,爲了唯一地標識該類型,還要在類型名稱前加上裝載該類型(指出它所位於的命名空間)的類裝載器標識。

 

2.3 方法區

在Java虛擬機中,關於被裝載類型的信息存儲在一個邏輯上被稱爲方法區的內存中。當虛擬機裝載某個類型時,它使用類裝載器定位相應的class文件,而後讀入這個class文件——1個線性二進制數據流,而後它傳輸到虛擬機中,緊接着虛擬機提取其中的類型信息,並將這些信息存儲到方法區。該類型中的類(靜態)變量一樣也是存儲在方法區中。

  JAVA虛擬機在內部如何存儲類型信息,這是由具體實現的設計者來決定的。

  當虛擬機運行Java程序時,它會查找使用存儲在方法區中的類型信息。因爲全部線程都共享方法區,所以它們對方法區數據的訪問必須被設計爲是線程安全的。好比,假設同時有兩個線程都企圖訪問一個名爲Lava的類,而這個類尚未被裝入虛擬機,那麼,這時只應該有一個線程去裝載它,而另外一個線程則只能等待。

  對於每一個裝載的類型,虛擬機都會在方法區中存儲如下類型信息:

  • 這個類型的全限定名

  • 這個類型的直接超類的全限定名(除非這個類型是java.lang.Object,它沒有超類)

  • 這個類型是類類型仍是接口類型

  • 這個類型的訪問修飾符(public、abstract或final的某個子集)

  • 任何直接超接口的全限定名的有序列表

除了上面列出的基本類型信息外,虛擬機還得爲每一個被裝載的類型存儲如下信息:

  • 該類型的常量池

  • 字段信息

  • 方法信息

  • 除了常量之外的全部類(靜態)變量

  • 一個到類ClassLoader的引用

  • 一個到Class類的引用

2.3.1 常量池

虛擬機必須爲每一個被裝載的類型維護一個常量池。常量池就是該類型所用常量的一個有序集合,包括直接常量對其餘類型、字段和方法的符號引用。池中的數據項就像數組同樣是經過索引訪問的。由於常量池存儲了相應類型所用到的全部類型、字段和方法的符號引用,因此它在Java程序的動態鏈接中起着核心的做用。

2.3.2 字段信息

對於類型中聲明的每個字段。方法區中必須保存下面的信息。除此以外,這些字段在類或者接口中的聲明順序也必須保存。

  • 字段名

  • 字段的類型

  • 字段的修飾符(public、private、protected、static、final、volatile、transient的某個子集)

2.3.3 方法信息

對於類型中聲明的每個方法,方法區中必須保存下面的信息。和字段同樣,這些方法在類或者接口中的聲明順序也必須保存。

  • 方法名

  • 方法的返回類型(或void)

  • 方法參數的數量和類型(按聲明順序)

  • 方法的修飾符(public、private、protected、static、final、synchronized、native、abstract的某個子集)

除了上面清單中列出的條目以外,若是某個方法不是抽象的和本地的,它還必須保存下列信息:

  • 方法的字節碼(bytecodes)

  • 操做數棧和該方法的棧幀中的局部變量區的大小

  • 異常表

2.3.4 類(靜態)變量

類變量是由全部類實例共享的,可是即便沒有任何類實例,它也能夠被訪問。這些變量只與類有關——而非類的實例,所以它們老是做爲類型信息的一部分而存儲在方法區。除了在類中聲明的編譯時常量外,虛擬機在使用某個類以前,必須在方法區中爲這些類變量分配空間。

  而編譯時常量(就是那些用final聲明以及用編譯時已知的值初始化的類變量)則和通常的類變量處理方式不一樣,每一個使用編譯時常量的類型都會複製它的全部常量到本身的常量池中,或嵌入到它的字節碼流中。做爲常量池或字節碼流的一部分,編譯時常量保存在方法區中——就和通常的類變量同樣。可是當通常的類變量做爲聲明它們的類型的一部分數據面保存的時候,編譯時常量做爲使用它們的類型的一部分而保存。

2.3.5 指向ClassLoader類的引用

  每一個類型被裝載的時候,虛擬機必須跟蹤它是由啓動類裝載器仍是由用戶自定義類裝載器裝載的。若是是用戶自定義類裝載器裝載的,那麼虛擬機必須在類型信息中存儲對該裝載器的引用。這是做爲方法表中的類型數據的一部分保存的。

  虛擬機會在動態鏈接期間使用這個信息。當某個類型引用另外一個類型的時候,虛擬機會請求裝載發起引用類型的類裝載器來裝載被引用的類型。這個動態鏈接的過程,對於虛擬機分離命名空間的方式也是相當重要的。爲了可以正確地執行動態鏈接以及維護多個命名空間,虛擬機須要在方法表中得知每一個類都是由哪一個類裝載器裝載的。

2.3.6 指向Class類的引用

對於每個被裝載的類型(無論是類仍是接口),虛擬機都會相應地爲它建立一個java.lang.Class類的實例,並且虛擬機還必須以某種方式把這個實例和存儲在方法區中的類型數據關聯起來。

  在Java程序中,你能夠獲得並使用指向Class對象的引用。Class類中的一個靜態方法可讓用戶獲得任何已裝載的類的Class實例的引用。

public static Class<?> forName(String className)

  好比,若是調用forName("java.lang.Object"),那麼將獲得一個表明java.lang.Object的Class對象的引用。可使用forName()來獲得表明任何包中任何類型的Class對象的引用,只要這個類型能夠被(或者已經被)裝載到當前命名空間中。若是虛擬機沒法把請求的類型裝載到當前命名空間,那麼會拋出ClassNotFoundException異常。

  另外一個獲得Class對象引用的方法是,能夠調用任何對象引用的getClass()方法。這個方法被來自Object類自己的全部對象繼承:

public final native Class<?> getClass();

好比,若是你有一個到java.lang.Integer類的對象的引用,那麼你只需簡單地調用Integer對象引用的getClass()方法,就能夠獲得表示java.lang.Integer類的Class對象。

2.3.7 方法區使用示例

示例爲了展現虛擬機如何使用方法區的信息:

class A {
    private int speed = 5;
    void run(){}
}

public class B {
    public static void main(String[] args){
        A a = new A();
        a.run();
    }
}

不一樣的虛擬機實現可能會用徹底不一樣的方法來操做,下面描述的只是其中一種可能——但並非僅有的一種。

  要運行B程序,首先得以某種「依賴於實現的」方式告訴虛擬機「B」這個名字。以後,虛擬機將找到並讀入相應的class文件「B.class」,而後它會從導入的class文件裏的二進制數據中提取類型信息並放到方法區中。經過執行保存在方法區中的字節碼,虛擬機開始執行main()方法,在執行時,它會一直持有指向當前類(B類)的常量池(方法區中的一個數據結構)的指針。

  注意:虛擬機開始執行B類中main()方法的字節碼的時候,儘管A類還沒被裝載,可是和大多數(也許全部)虛擬機實現同樣,它不會等到把程序中用到的全部類都裝載後纔開始運行。剛好相反,它只會須要時才裝載相應的類。

  main()的第一條指令告知虛擬機爲列在常量池第一項的類分配足夠的內存。因此虛擬機使用指向B常量池的指針找到第一項,發現它是一個對A類的符號引用,而後它就檢查方法區,看A類是否已經被加載了。

  這個符號引用僅僅是一個給出了類A的全限定名「A」的字符串。爲了能讓虛擬機儘量快地從一個名稱找到類,虛擬機的設計者應當選擇最佳的數據結構和算法。

  當虛擬機發現尚未裝載過名爲「A」的類時,它就開始查找並裝載文件「A.class」,並把從讀入的二進制數據中提取的類型信息放在方法區中。

  緊接着,虛擬機以一個直接指向方法區A類數據的指針來替換常量池第一項(就是那個字符串「A」),之後就能夠用這個指針來快速地訪問A類了。這個替換過程稱爲常量池解析,即把常量池中的符號引用替換爲直接引用。

  終於,虛擬機準備爲一個新的A對象分配內存。此時它又須要方法區中的信息。還記得剛剛放到B類常量池第一項的指針嗎?如今虛擬機用它來訪問A類型信息,找出其中記錄的這樣一條信息:一個A對象須要分配多少堆空間。

  JAVA虛擬機總可以經過存儲與方法區的類型信息來肯定一個對象須要多少內存,當JAVA虛擬機肯定了一個A對象的大小後,它就在堆上分配這麼大的空間,並把這個對象實例的變量speed初始化爲默認初始值0。

  當把新生成的A對象的引用壓到棧中,main()方法的第一條指令也完成了。接下來的指令經過這個引用調用Java代碼(該代碼把speed變量初始化爲正確初始值5)。另外一條指令將用這個引用調用Lava對象引用的run()方法。

 

2.4 堆

Java程序在運行時建立的全部類實例或數組都放在同一個堆中。而一個JAVA虛擬機實例中只存在一個堆空間,所以全部線程都將共享這個堆。又因爲一個Java程序獨佔一個JAVA虛擬機實例,於是每一個Java程序都有它本身的堆空間——它們不會彼此干擾。可是同一個Java程序的多個線程卻共享着同一個堆空間,在這種狀況下,就得考慮多線程訪問對象(堆數據)的同步問題了。

  JAVA虛擬機有一條在堆中分配新對象的指令,卻沒有釋放內存的指令,正如你沒法用Java代碼區明確釋放一個對象同樣。虛擬機本身負責決定如何以及什麼時候釋放再也不被運行的程序引用的對象所佔據的內存。一般,虛擬機把這個任務交給垃圾收集器。

2.4.1 數組的內部表示

在Java中,數組是真正的對象。和其餘對象同樣,數組老是存儲在堆中。一樣,數組也擁有一個與它們的類相關聯的Class實例,全部具備相同維度和類型的數組都是同一個類的實例,而無論數組的長度(多維數組每一維的長度)是多少。例如一個包含3個int整數的數組和一個包含300個整數的數組擁有同一個類。數組的長度只與實例數據有關。

  數組類的名稱由兩部分組成:每一維用一個方括號「[」表示,用字符或字符串表示元素類型。好比,元素類型爲int整數的、一維數組的類名爲「[I」,元素類型爲byte的三維數組爲「[[[B」,元素類型爲Object的二維數組爲「[[Ljava/lang/Object」。

  多維數組被表示爲數組的數組。好比,int類型的二維數組,將表示爲一個一維數組,其中的每個元素是一個一維int數組的引用,以下圖:
Dzx+EWYzHz00AAAAAElFTkSuQmCC

在堆中的每一個數組對象還必須保存的數據時數組的長度、數組數據,以及某些指向數組的類數據的引用。虛擬機必須可以經過一個數組對象的引用獲得此數組的長度,經過索引訪問其元素(期間要檢查數組邊界是否越界),調用全部數組的直接超類Object聲明的方法等等。

 

2.5 程序計數器

對於一個運行中的Java程序而言,其中的每個線程都有它本身的PC(程序計數器)寄存器,它是在該線程啓動時建立的,PC寄存器的大小是一個字長,所以它既可以持有一個本地指針,也可以持有一個returnAddress。當線程執行某個Java方法時,PC寄存器的內容老是下一條將被執行指令的「地址」,這裏的「地址」能夠是一個本地指針,也能夠是在方法字節碼中相對於該方法起始指令的偏移量。若是該線程正在執行一個本地方法,那麼此時PC寄存器的值是「undefined」。

 

2.6 Java棧

每當啓動一個新線程時,Java虛擬機都會爲它分配一個Java棧。Java棧以幀爲單位保存線程的運行狀態。虛擬機只會直接對Java棧執行兩種操做:以幀爲單位的壓棧和出棧。

  某個線程正在執行的方法被稱爲該線程的當前方法,當前方法使用的棧幀稱爲當前幀,當前方法所屬的類稱爲當前類,當前類的常量池稱爲當前常量池。在線程執行一個方法時,它會跟蹤當前類和當前常量池。此外,當虛擬機遇到棧內操做指令時,它對當前幀內數據執行操做。

  每當線程調用一個Java方法時,虛擬機都會在該線程的Java棧中壓入一個新幀。而這個新幀天然就成爲了當前幀。在執行這個方法時,它使用這個幀來存儲參數、局部變量、中間運算結果等數據。

  Java方法能夠以兩種方式完成。一種經過return返回的,稱爲正常返回;一種是經過拋出異常而異常終止的。無論以哪一種方式返回,虛擬機都會將當前幀彈出Java棧而後釋放掉,這樣上一個方法的幀就成爲當前幀了。

  Java幀上的全部數據都是此線程私有的。任何線程都不能訪問另外一個線程的棧數據,所以咱們不須要考慮多線程狀況下棧數據的訪問同步問題。當一個線程調用一個方法時,方法的的局部變量保存在調用線程Java棧的幀中。只有一個線程能老是訪問那些局部變量,即調用方法的線程。

 

2.7 本地方法棧

前面提到的全部運行時數據區都是Java虛擬機規範中明肯定義的,除此以外,對於一個運行中的Java程序而言,它還可能會用到一些跟本地方法相關的數據區。當某個線程調用一個本地方法時,它就進入了一個全新的而且再也不受虛擬機限制的世界。本地方法能夠經過本地方法接口來訪問虛擬機的運行時數據區,但不止如此,它還能夠作任何它想作的事情。

  本地方法本質上時依賴於實現的,虛擬機實現的設計者們能夠自由地決定使用怎樣的機制來讓Java程序調用本地方法。

  任何本地方法接口都會使用某種本地方法棧。當線程調用Java方法時,虛擬機會建立一個新的棧幀並壓入Java棧。然而當它調用的是本地方法時,虛擬機會保持Java棧不變,再也不在線程的Java棧中壓入新的幀,虛擬機只是簡單地動態鏈接並直接調用指定的本地方法。

  若是某個虛擬機實現的本地方法接口是使用C鏈接模型的話,那麼它的本地方法棧就是C棧。當C程序調用一個C函數時,其棧操做都是肯定的。傳遞給該函數的參數以某個肯定的順序壓入棧,它的返回值也以肯定的方式傳回調用者。一樣,這就是虛擬機實現中本地方法棧的行爲。

  極可能本地方法接口須要回調Java虛擬機中的Java方法,在這種狀況下,該線程會保存本地方法棧的狀態並進入到另外一個Java棧。

  下圖描繪了這樣一個情景,就是當一個線程調用一個本地方法時,本地方法又回調虛擬機中的另外一個Java方法。這幅圖展現了JAVA虛擬機內部線程運行的全景圖。一個線程可能在整個生命週期中都執行Java方法,操做它的Java棧;或者它可能毫無障礙地在Java棧和本地方法棧之間跳轉。  

BzZxI6DO5BbIAAAAAElFTkSuQmCC

  該線程首先調用了兩個Java方法,而第二個Java方法又調用了一個本地方法,這樣致使虛擬機使用了一個本地方法棧。假設這是一個C語言棧,其間有兩個C函數,第一個C函數被第二個Java方法當作本地方法調用,而這個C函數又調用了第二個C函數。以後第二個C函數又經過本地方法接口回調了一個Java方法(第三個Java方法),最終這個Java方法又調用了一個Java方法(它成爲圖中的當前方法)。

 

轉載 http://www.cnblogs.com/java-my-life/archive/2012/08/01/2615221.html

相關文章
相關標籤/搜索