JVM的結構

煲煲菜發表於2018-11-12

JVM的結構

結合《深入理解Java虛擬機器:高階特性及最佳實踐》、《實戰Java虛擬機器——JVM故障診斷與效能優化》、油管視訊,下圖是我對JVM結構的總結:

JVM的結構

要理解JVM的結構,其實可以從java程式怎麼執行的角度去理解:

java程式執行的是class檔案,所以需要類裝載子系統來把class檔案載入到記憶體中執行,而class檔案具體是載入存放到jvm中一塊叫方法區的記憶體空間,方法區除了存放類資訊外,還劃分了一塊叫執行時常量池的區域,用來存放字串字面量、數字常量等資訊。

class檔案載入了,記憶體中有類資訊了,java程式中就可以建立物件。那麼物件又存哪裡?當然是我們耳熟能詳的堆記憶體了。另外,除了堆記憶體,還有一塊直接記憶體,這塊記憶體是堆外的記憶體,是直接向系統申請的記憶體空間,這塊記憶體訪問速度比堆記憶體塊,比如Java的NIO庫就使用了直接記憶體。

上面幾樣都是跟程式的資料儲存相關的,而程式需要實現執行,以下的就是相關的:

Java棧: Java是多執行緒的,一條執行的執行緒就會有一個對應的Java棧。而一個執行緒跑的程式,會呼叫很多的函式,一個函式就對應Java棧中一個棧幀。棧幀存放著函式執行需要的東西,棧幀包含了:區域性變數表運算元棧幀資料區等等。幀資料區是用來支援棧幀做常量池解析、方法返回、異常處理的。比如當Java位元組碼需要訪問常量池時,幀資料區儲存則常量池的指標,方便程式訪問常量池;比如異常處理,幀資料區中會有一個異常處理表,方便在發生異常時找到處理異常的程式碼。

Exception table:
from    to  target  type
4       16  19      any
19      21  19      any
複製程式碼

上面的異常處理表,表示位元組碼偏移量4~16位元組可能丟擲異常,如果丟擲異常,跳轉到偏移量19的地方繼續執行。

程式計數器: 英文名是Program Counter Register,直譯是“程式計數器暫存器”,所以國內的部落格、書籍叫法有些統一。有的叫“程式計數器”,有的叫“PC暫存器”。我這裡參考《深入理解Java虛擬機器-JVM高階特性與最佳實踐》的表述方法,叫程式計數器。程式計數器就是用來記錄執行緒當前執行到哪一行位元組碼的。

本地方法棧: 結構跟Java棧差不多,區別就是它是用來執行本地方法的。

以上的方法區、Java堆、程式計數器、Java棧、本地方法棧構成了JVM的執行時資料區

有了執行時資料區來存放Java程式執行的所有資料,就可以用執行引擎來把程式執行跑起來了。比如Interpreter是解析器,用來讀取執行位元組碼的指令;JIT Compiler是即時編譯器,可以即時把位元組碼指令編譯機器碼,彌補了Interpreter的效能不足;GC就是垃圾收集器,用來收集在執行時產生的垃圾記憶體。

關於區域性變數表的一個細節

我在看《實戰Java虛擬機器-JVM故障診斷與效能優化》時看到一些有趣的地方,做了測試,順便記錄下來。

細節1:不同編譯器編譯出來區域性變數表的大小有可能不一樣。

public class TestStackDeep {
    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;
        count++;
        recursion(a, b, c);
    }
    public static void main(String args[]) {
        try {
			recursion(0L,0L,0L);
        } catch (Throwable e) {
            System.out.println("deep of calling = " + count);
            e.printStackTrace();
        }
    }
}
複製程式碼

上面程式碼中的recursion方法,有3個long型別的形參,方法中還定義了10個區域性變數。在Intellij IDEA中使用jclasslib的外掛可以檢視到這個方法的區域性變數表的大小:

這裡是在Intellij IDEA中使用jdk7的javac編譯器編譯出來的,區域性變數表佔26個字:

JVM的結構
JVM的結構

JVM的結構

改成eclipse編譯器後,區域性變數表佔6個字:

JVM的結構

JVM的結構
看來eclipse編譯器是做了優化,在recursion方法中定義的10個區域性變數都沒有使用過,所以直接就去掉了,只剩下6個字

“字”是指計算機記憶體中佔據的一個單獨的記憶體單元編號的一組二進位制串。一般32位計算機上一個字為4個位元組長度

細節2:區域性變數表的槽位

public class LocalVar {
    public void localvar1() {
        int a = 0;
        System.out.println(a);
        int b = 0;
    }

    public void localvar2() {
        {
            int a = 0;
            System.out.println(a);
        }
        int b = 0;
    }

    public static void main(String[] args) {

    }
}
複製程式碼

還是使用jclasslib來檢視區域性變數表。其中,

localvar1的區域性變數表(最大佔3個字):

JVM的結構

localvar1的區域性變數表(最大佔2個字):

JVM的結構

注意看localvar1的Index列顯示佔的槽位是0,1,2三個槽位,一個槽位佔一個字,所以localvar1的區域性變數表最大佔3個字;

而注意看localvar2的Index列顯示佔的槽位是1,0,1,其中槽位“1”複用了,這叫叫槽位複用,所以localvar2的區域性變數表最大佔2個字

相關文章