国产成人精品18p,天天干成人网,无码专区狠狠躁天天躁,美女脱精光隐私扒开免费观看

如何理解java虛擬機的基本結構

發(fā)布時(shí)間:2021-09-27 17:50 來(lái)源:億速云 閱讀:0 作者:柒染 欄目: 開(kāi)發(fā)技術(shù)

今天就跟大家聊聊有關(guān)如何理解java虛擬機的基本結構,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。

1. java 虛擬機的架構

  • 類(lèi)加載子系統:負責從文件系統或者網(wǎng)絡(luò )中加載class信息,加載的類(lèi)信息存放于一塊稱(chēng)為方法區的內存空間中。除了類(lèi)的信息,方法區中可能還會(huì )存放運行時(shí)常量池信息,包括字符串字面量和數字常量(這部分常量信息是class文件中常量池部分的內存映射)。

  • java堆:java堆在虛擬機啟動(dòng)的時(shí)候建立,它是java程序最主要的內存工作區域。幾乎所有的java對象實(shí)例都存放于java堆中。堆空間是所有線(xiàn)程共享的,這是一塊與java應用密切相關(guān)的內存區域。

  • 直接內存:java的NIO庫允許程序使用直接內存。直接內存是在java堆外的、直接向系統申請的內存區域。通常,訪(fǎng)問(wèn)直接內存的速度會(huì )優(yōu)于java堆。因此,出于性能考慮,讀寫(xiě)頻繁的場(chǎng)合可能會(huì )考慮使用直接內存。由于直接內存在java堆外,因此,它的大小不會(huì )直接受限于Xmx指定的最大堆大小,但是系統內存是有限的,java堆和直接內存的總和依然受限于操作系統的最大內存。

  • 垃圾回收系統:垃圾回收系統是java虛擬機有重要組成部分,垃圾回收器可以對方法區、java堆和直接內存進(jìn)行回收。其中,java堆是垃圾收集器的工作重點(diǎn)。和 C/C++ 不同,java中所有的對象空間釋放都是隱式的。也就是說(shuō),java中沒(méi)有類(lèi)似 free() 或者 delete() 這樣的函數釋放指定的內存區域。對于不再使用的垃圾對象,垃圾回收系統會(huì )在后臺默默工作,默默查找、標識并釋放垃圾對象,完成包括java堆、方法區和直接內存中的全自動(dòng)化管理。

  • 每一個(gè)java虛擬機線(xiàn)程都有一個(gè)私有的java棧,一個(gè)線(xiàn)程的java棧在線(xiàn)程創(chuàng )建的時(shí)候被創(chuàng )建,java棧中保存著(zhù)幀信息,java棧中保存著(zhù)局部變量、方法參數,同時(shí)和java方法的調用、返回密切相關(guān)。

  • 本地方法棧和java棧非常類(lèi)似,最大的不同在于java棧用于方法的調用,而本地方法棧則用于本地方法的調用,作為對java虛擬機的重要擴展,java虛擬機允許java直接調用本地方法(通常使用C編寫(xiě))

  • PC(Program Counter)寄存器也是每一個(gè)線(xiàn)程私有的空間,java虛擬機會(huì )為每一個(gè)java線(xiàn)程創(chuàng )建PC寄存器。在任意時(shí)刻,一個(gè)java線(xiàn)程總是在執行一個(gè)方法,這個(gè)正在被執行的方法稱(chēng)為當前方法。如果當前方法不是本地方法,PC寄存器就會(huì )指向當前正在被執行的指令。如果當前方法是本地方法,那么PC寄存器的值就是undefined

  • 執行引擎是java虛擬機的最核心組件之一,它負責執行虛擬機的字節碼,現代虛擬機為了提高執行效率,會(huì )使用即時(shí)編譯技術(shù)將方法編譯成機器碼后再執行。

2. java堆

根據java回收機制的不同,java堆有可能擁有不同的結構。最為常見(jiàn)的一種構成是將整個(gè)java堆分為新生代和老年代。其中新生代存放新生對象或者年齡不大的對象,老年代則存放老年對象。新生代有可能分為eden區、s0區、s1區,s0區和s1區也被稱(chēng)為from和to區,他們是兩塊大小相同、可以互換角色的內存空間。

3. java棧

java棧是一塊線(xiàn)程私有的內存空間。如果說(shuō),java堆和程序數據密切相關(guān),那么java棧就是和線(xiàn)程執行密切相關(guān)。線(xiàn)程執行的基本行為是函數調用,每次函數調用的數據都是通過(guò)java棧傳遞的。

在java棧中保存的主要內容為棧幀。每一次函數調用,都會(huì )有一個(gè)對應的棧幀被壓入java棧,每一個(gè)函數調用結束,都會(huì )有一個(gè)棧幀被彈出java棧。如下圖:

函數1對應棧幀1,函數2對應棧幀2,依次類(lèi)推。當前正在執行的函數所對應的幀就是當前幀(位于棧頂),它保存著(zhù)當前函數的局部變量、中間計算結果等數據。

當函數返回時(shí),棧幀從java棧中被彈出,java方法區有兩種返回函數的方式,一種是正常的函數返回,使用return指令,另一種是拋出異常。不管使用哪種方式,都會(huì )導致棧幀被彈出。

java虛擬機提供了參數-Xss來(lái)指定線(xiàn)程的最大??臻g,這個(gè)參數也直接決定了函數調用的最大深度:

private static int count = 0;
public static void recursion() {
    count++;
    recursion();
}

public static void main(String[] args) {
    try{
        recursion();
    } catch (Throwable e) {
        System.out.println("deep of calling =" + count);
        e.printStackTrace();
    }
}

使用-Xss256K參數,結果如下:

可以看到,在進(jìn)行大約2900次調用后,發(fā)生了棧溢出錯誤,通過(guò)增大-Xss的值,可以獲得更深的調用層次,嘗試使用參數-Xss512K,可以看到調用次數明顯增加:

在一個(gè)棧幀中,至少包含局部變量表、操作數棧和幀數據區幾個(gè)部分。

1 局部變量表

局部變量表用于保存函數的參數以及局部變量。局部亦是表中的變量只在當前函數調用中有效,當函數調用結束后,函數棧幀銷(xiāo)毀,局部變量表也會(huì )隨之銷(xiāo)毀。

由于局部變量表在棧幀之中,因此,如果函數的參數和局部變量較多,會(huì )使局部變量表膨脹,從而每一次函數調用就會(huì )占用更多的??臻g,最終導致函數的嵌套調用次數減少。

下面這段代碼,第一個(gè)recursion() 函數有3個(gè)參數和10個(gè)局部變量,因此,其局部變量表含有13個(gè)變量,而第2個(gè)recursion()函數不含有任何參數和局部變量。當這兩個(gè)函數被嵌套調用時(shí),第2個(gè)rescursion()函數可以擁有更深的調用層次。

 private static int count = 0;

    public static void recursion(long a, long b, long c) {
        long e = 1, f = 2, g = 3, h = 4, i = 5, k = 6, q = 7, x = 8, y = 9, z = 10;
        recursion(a, b, c);
    }
    public static void recursion() {
        count++;
        recursion();
    }

    public static void main(String[] args) {
        try{
            recursion();
        } catch (Throwable e) {
            System.out.println("deep of calling = " + count);
            e.printStackTrace();
        }
    }

使用-Xss256k 執行上述代碼中的第1個(gè)rescursion() 函數,結果如下:

使用-Xss256k 執行上述代碼中的第2個(gè)rescursion() 函數,結果如下:

可以看到,在相同的棧容量下,局部變量少的函數可以支持更深層次的函數調用。

棧楨中的局部變量表中的槽位是可以重用的,如果局部變量的作用域范圍超過(guò)了其作用域,那么在其作用域之后聲明的新的局部變量就很有可能會(huì )復用局部變量a的槽位,從而達到節省資源的目的。局部變量表中的變量也是重要的垃圾回收根節點(diǎn),被局部變量表中直接或間接引用的對象都是不會(huì )回收的。

如以下代碼:

public void localVarGc1() {
        byte[] a = new byte[6 * 1024 * 1024];
        System.gc();
    }

    public void localVarGc2() {
        byte[] a = new byte[6 * 1024 * 1024];
        a = null;
        System.gc();
    }

    public void localVarGc3() {
        {
            byte[] a = new byte[6 * 1024 * 1024];
        }
        System.gc();
    }

    public void localVarGc4() {
        {
            byte[] a = new byte[6 * 1024 * 1024];
        }
        int c = 10;
        System.gc();
    }

    public void localVarGc5() {
        localVarGc1();
        System.gc();
    }

    public static void main(String[] args) {
        Demo05 d = new Demo05();
        d.localVarGc1();
        //d.localVarGc2();
        //d.localVarGc3();
        //d.localVarGc4();
        //d.localVarGc5();
    }

上述代碼中,每一個(gè)localVarGcN()函數都分配了一塊6MB的堆內存,并使用局部變量引用這塊空間??梢允褂脜?code>-XX:+PrintGC 分別執行上述函數,在輸出的日志中,可以看到垃圾回收前后堆的大小,進(jìn)而推斷byte數組是否被回收。

  • localVarGc1()中,在申請空間后,立即進(jìn)行垃圾回收,很多明顯,由于byte數組被變量a引用,因此無(wú)法回收這塊空間。執行結果如下:

[GC (System.gc())  8765K->6664K(251392K), 0.0041586 secs]
[Full GC (System.gc())  6664K->6515K(251392K), 0.0039022 secs]
  • localVarGc2()中,在垃圾回收前,先將變量a置為null,使用byte數組失去強引用,故垃圾回收可以順利回收byte數組。執行結果如下:

[GC (System.gc())  8765K->568K(251392K), 0.0012696 secs]
[Full GC (System.gc())  568K->395K(251392K), 0.0039405 secs]
  • 對于localVarGc3(),在垃圾回收前,先使用局部變量a失效,雖然變量a已經(jīng)離開(kāi)了作用域,但是變量a依然存在于局部變量表中,并且也指向這塊byte數組,故byte數組依然無(wú)法被回收。執行結果如下:

[GC (System.gc())  8765K->6696K(251392K), 0.0039619 secs]
[Full GC (System.gc())  6696K->6515K(251392K), 0.0039020 secs]
  • 對于localVarGc4(),在垃圾回收前,不僅使用變量a失效,更是聲明了變量c,使變量c復用了變量a的字,由于變量a此時(shí)被銷(xiāo)毀,故垃圾回收器可以順利回收byte數組。執行結果如下:

[GC (System.gc())  8765K->536K(251392K), 0.0010555 secs]
[Full GC (System.gc())  536K->370K(251392K), 0.0033685 secs]
  • 對于localVarGc5(),它首先調用了localVarGC1(),很明顯,在localVarGc1()中并沒(méi)有釋放byte數組,但在localVarGc1()返回后,它的棧楨被銷(xiāo)毀,自然也包含了棧幀中的所有局部變量,故byte數組失去引用,在localVarGc5()的垃圾回收中被回收。執行結果如下:

[GC (System.gc())  8765K->6744K(251392K), 0.0034826 secs]
[Full GC (System.gc())  6744K->6539K(251392K), 0.0045563 secs]
[GC (System.gc())  6539K->6539K(251392K), 0.0007713 secs]
[Full GC (System.gc())  6539K->395K(251392K), 0.0032212 secs]
2. 操作數棧

操作數棧主要用于保存計算過(guò)程的中間結果,同事作為計算過(guò)程中變量臨時(shí)的存儲空間。操作數棧也是一個(gè)先進(jìn)后出的數據結構,只支持入棧和出棧兩種操作。

3. 幀數據區

幀數據區時(shí)候為了支持常量池解析、正常方法返回和異常處理等。大部分Java字節碼指令需要進(jìn)行常量池訪(fǎng)問(wèn),在幀數據區中保存著(zhù)訪(fǎng)問(wèn)常量池的指針,方便程序訪(fǎng)問(wèn)常量池。

提示:由于每次函數調用都會(huì )產(chǎn)生對應的棧幀,從而占用一定的??臻g,因此,如果??臻g不足,那么函數調用自然無(wú)法繼續進(jìn)行下去。當請求的棧深度大于最大可用棧深度時(shí),系統會(huì )拋出StackOverflowError棧溢出錯誤。 舉個(gè)例子:

4. 棧上分配

棧上分配是Java虛擬機提供的一項優(yōu)化技術(shù),它的基本思想是:對于那些線(xiàn)程私有的對象(這里指不可能被其他線(xiàn)程訪(fǎng)問(wèn)的對象),可以將它們打散分配在棧上,而不是分配在堆上。分配在棧上的好處是可以在函數調用結束后自行銷(xiāo)毀,而不需要垃圾回收器的介入,從而提高系統的性能。

棧上分配的以及技術(shù)基礎是進(jìn)行逃逸分析。逃逸分析的目的是判斷對象的作用域是否有可能逃逸出函數體。

下面這個(gè)簡(jiǎn)單示例顯示了對非逃逸對象的棧上分配:

public static class User {
    public int  id;
    public String name = "";
}

public static void alloc() {
    User u = new User();
    u.id = 5;
    u.name = "geym0909";
}

public static void main(String[] args) {
    long b = System.currentTimeMillis();
    for(int i = 0; i < 10_0000_0000; i++) {
        alloc();
    }
    long e = System.currentTimeMillis();
    System.out.println(e - b);
}

上述代碼在主函數中進(jìn)行了1億次alloc()調用來(lái)創(chuàng )建對象,由于User對象實(shí)例需要占用約16字節的空間,因此累計分配空間將近1.5GB。如果堆空間小于這個(gè)值,就必然會(huì )發(fā)生GC。使用如下參數運行上述代碼:

-server -Xmx10m -Xms10m -XX:+PrintGC -XX:+DoEscapeAnalysis -XX:-UseTLAB -XX:+EliminateAllocations
  • 這里使用參數-server執行程序,因為在Server模式下,才可以啟用逃逸分析。

  • 參數-XX:+DoEscapeAnalysis啟用逃逸分析。

  • -Xms10m、-Xmx10m指定了最大與最小堆空間都是10m

  • -XX:+PrintGC將打印GC日志

  • -XX:+EliminateAllocations 開(kāi)啟了標量替換(默認打開(kāi)),允許將對象打散分配在棧上,比如對象擁有id與name兩個(gè)字段,那么這兩個(gè)字段將會(huì )被視為兩個(gè)獨立的局部變量進(jìn)行分配。

  • -XX:-UseTLAB關(guān)閉TLAB

程序執行后,結果如下:

注:在本人機器上,使用如下參數(即不指定任何棧上分配相關(guān)的參數),結果依然無(wú)大量gc日志:

-server -Xmx10m -Xms10m -XX:+PrintGC

再關(guān)閉逃逸分析,則結果如下:

-server -Xmx10m -Xms10m -XX:+PrintGC -XX:-DoEscapeAnalysis

可見(jiàn),在本人機器上逃逸分析、棧上分配是默認開(kāi)啟的。

對于大量的零散小對象,棧上分配提供了一種很好的對象分配優(yōu)化策略,棧上分配速度快,并且可以有效避免垃圾回收帶來(lái)的負面影響,但由于和堆空間相比,??臻g較小,因此,大對象無(wú)法也不適用在棧上分配。

5. 方法區

和堆一樣,方法區是一塊所有線(xiàn)程共享的內存區域,它用于保存系統的類(lèi)信息,比如類(lèi)的字段、方法、常量池等。方法區的大小決定了系統可以保存多少個(gè)類(lèi),如果系統定義了太多的類(lèi),導致方法區的溢出,虛擬機同樣會(huì )拋出內存溢出錯誤。

在JDK1.6、JDK1.7中,方法區可以理解為永久區(Perm)。永久區可以使用參數 -XX:PermSize-XX:MaxPermSize 指定,默認情況下,-XX:MaxPermSize 為64M。一個(gè)大的永久區可以保存更多的類(lèi)信息。如果系統使用了一些動(dòng)態(tài)代理,那么有可能會(huì )在運行時(shí)生成大量的類(lèi),如果這樣,就需要設置一個(gè)合理的永久區大小,確保不發(fā)生永久區內存溢出。

在JDK1.8中,永久區已經(jīng)被徹底移除,取而代之的是元數據區,元數據區大小可以使用參數 -XX:MaxMetaspaceSize 指定(一個(gè)大的元數據區可以使系統支持更多的類(lèi)),這是一塊堆外的直接內存。與永久區不同,如果不指定大小,默認情況下,虛擬機會(huì )耗盡所有的可用系統內存。

如果元數據區發(fā)生異常,虛擬機一樣會(huì )拋出異常。

4. java虛擬機參數總結

  • -server:使用server模式啟動(dòng)jvm,對應也有-client,使用client模式啟動(dòng)jvm。對于server模式,jvm啟動(dòng)較慢,因為jvm會(huì )收集系統信息并進(jìn)行優(yōu)化在提高程序的運行效率;對于client模式,jvm啟動(dòng)較快,但由于沒(méi)有收集運行時(shí)的信息導致優(yōu)化不足,后期運行效率可能會(huì )降低。

  • 參數-XX:+DoEscapeAnalysis啟用逃逸分析。

  • -Xms10m、-Xmx10m指定了最大與最小堆空間都是10m

  • -Xss256k:指定棧大小為256k

  • -XX:+PrintGC將打印GC日志

  • -XX:+EliminateAllocations 開(kāi)啟了標量替換(默認打開(kāi)),允許將對象打散分配在棧上,比如對象擁有id與name兩個(gè)字段,那么這兩個(gè)字段將會(huì )被視為兩個(gè)獨立的局部變量進(jìn)行分配。

  • -XX:-UseTLAB關(guān)閉TLAB

  • -XX:PermSize-XX:MaxPermSize:在JDK1.6、JDK1.7中,方法區可以理解為永久區(Perm)。永久區可以使用參數 -XX:PermSize-XX:MaxPermSize 指定。默認情況下,-XX:MaxPermSize 為64M。

  • -XX:MaxMetaspaceSize:在JDK1.8中,永久區已經(jīng)被徹底移除,取而代之的是元數據區,元數據區大小可以使用參數 -XX:MaxMetaspaceSize 指定。這是一塊堆外的直接內存。與永久區不同,如果不指定大小,默認情況下,虛擬機會(huì )耗盡所有的可用系統內存。


免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。

国内偷窥一区二区三区视频| 最近免费中文字幕大全高清大全10| 国产美女爽到喷出水来视频| 久久综合国产乱子伦精品免费 | 国产裸体XXXX视频在线播放| 国产精久久一区二区三区|