四 字節(jié)碼執(zhí)行引擎

目錄

1.class文件結(jié)構(gòu)
2.常用字節(jié)碼指令
3.運行時棧幀結(jié)構(gòu)(局部變量表,操作數(shù)棧,動態(tài)鏈接, 方法返回地址,附加信息)
4.方法調(diào)用(解析,分派,動態(tài)語言支持)

1.class 文件結(jié)構(gòu)

魔數(shù),版本,常量池,訪問符,類、超類、接口,字段,方法,屬性

自己寫了一個字節(jié)碼解析器,直觀體驗下:

public static String format(byte[] bt) {
        int line = 0;
        StringBuilder buf = new StringBuilder();

        // 跳過魔數(shù),版本號
        int constNum = getConstPoolNum(bt);

        String[] constant = new String[constNum]; // 常量池表項
        int[] constLen = new int[constNum]; // 對應(yīng)長度

        int finishedVisitAllConsts = initConstPool(constant, constLen, bt);// 最后一次循環(huán)走完
                                                                            // 此時line為Class的訪問標記
        int AccessFlag = 2; // 訪問符
        int index = finishedVisitAllConsts + AccessFlag;// 到class info
        index = dealWithClassInfo(bt, index, constant); // 到接口的位置
        index = dealWithInterfaceInfo(bt, index, constant); // 到Field的位置
        index = dealWithFieldInfo(bt, index, constant);// 到METHOD位置
        index = dealWithMethodInfo(bt, index, constant);// 到最后的ATTRIBUTE位置
        index = dealWithEndInfo(bt, index, constant);
        System.out.println();
        System.out.println("檢查遍歷完成:" + index + "==" + (bt.length - 1));
        line = 0;
        for (byte d : bt) {
            if (line % 16 == 0)
                buf.append(String.format("%05x: ", line));
            buf.append(String.format("%02x  ", d));
            line++;
            if (line % 16 == 0)
                buf.append("\n");
        }
        buf.append("\n");
        return buf.toString();
    }

這篇文章解釋的很清楚
讀上面這篇文章要有耐心,讀懂之后,可以結(jié)合我下面的程序,進行自己調(diào)試。讓你們更好的理解CLASS文件結(jié)構(gòu);我這個程序沒有寫全,有些屬性表的東西,沒有做很好的解析,你們可以自行補充。

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

public class ClassReader {
    static int[] tag = { 1, 3, 7, 8, 9, 10, 11, 12 };// 1 UTF8,7 CLASS,9
                                                        // FIELDREF,10
                                                        // MethodRef,12 Name and
                                                        // Type
    static int[] tagLen = { 2, 4, 2, 2, 4, 4, 4, 4 };
    static Map<Integer, Integer> map = new HashMap<Integer, Integer>();

    static String[] accessMethodFlags = { "public", "private", "protected",
            "static", "final", "synchronized", "bridge", "varargs", "native",
            "abstract", "strictfp", "synthetic" };

    static String[] accessFieldFlags = { "public", "private", "protected",
            "static", "final", "", "volatile", "transient", "synthetic", "enum" };
    static {
        for (int i = 0; i < tag.length; i++)
            // 添加各tag長度
            map.put(tag[i], tagLen[i]);

    }

    private static int getConstPoolNum(byte[] bt) {
        int constNum = 0; // 常量池表項的個數(shù)
        int i = 0;
        for (byte d : bt) {
            if (i == 8)
                constNum = d; // 第9、10個字節(jié)為常量池大小
            if (i == 9)
                constNum += d - 1;// 化為10進制-1為常量池表項的個數(shù)
            i++;
        }
        System.out.println("常量池個數(shù):" + constNum);
        return constNum;
    }

    public static String format(byte[] bt) {
        int line = 0;
        StringBuilder buf = new StringBuilder();

        // 跳過魔數(shù),版本號
        int constNum = getConstPoolNum(bt);

        String[] constant = new String[constNum]; // 常量池表項
        int[] constLen = new int[constNum]; // 對應(yīng)長度

        int finishedVisitAllConsts = initConstPool(constant, constLen, bt);// 最后一次循環(huán)走完
                                                                            // 此時line為Class的訪問標記
        int AccessFlag = 2; // 訪問符
        int index = finishedVisitAllConsts + AccessFlag;// 到class info
        index = dealWithClassInfo(bt, index, constant); // 到接口的位置
        index = dealWithInterfaceInfo(bt, index, constant); // 到Field的位置
        index = dealWithFieldInfo(bt, index, constant);// 到METHOD位置
        index = dealWithMethodInfo(bt, index, constant);// 到最后的ATTRIBUTE位置
        index = dealWithEndInfo(bt, index, constant);
        System.out.println();
        System.out.println("檢查遍歷完成:" + index + "==" + (bt.length - 1));
        line = 0;
        for (byte d : bt) {
            if (line % 16 == 0)
                buf.append(String.format("%05x: ", line));
            buf.append(String.format("%02x  ", d));
            line++;
            if (line % 16 == 0)
                buf.append("\n");
        }
        buf.append("\n");
        return buf.toString();
    }

    private static int dealWithEndInfo(byte[] bt, int index, String[] constant) {
        int cnt = bt[index + 2];
        index += 2;
        System.out.println("剩余屬性數(shù)量:" + cnt);
        for (int i = 0; i < cnt; i++) {
            String name = constant[bt[index + 2] - 1];
            System.out.print("名字:" + name);
            index += 2;
            System.out.print(" ,長度:" + bt[index + 4]);
            index += 4;
            if ("SourceFile".equals(name)) {
                System.out
                        .print(" ,內(nèi)容:"
                                + constant[Integer
                                        .parseInt(constant[bt[index] - 1]) - 1]);
            }
            index += bt[index];
            System.out.println();
        }
        return index;
    }

    private static int dealWithMethodInfo(byte[] bt, int index,
            String[] constant) {
        int numOfMethod = bt[index];
        numOfMethod += bt[++index];
        // 打印方法
        if (numOfMethod == 0) {
            System.out.println(" - - - 無方法 - - - ");
        } else {
            System.out.println("方法數(shù)為:" + numOfMethod);
            // 每個方法 前2個字節(jié)為 flag 之后2個字節(jié)為 name 之后2個字節(jié)為描述
            // 還有2個字節(jié)描述其他屬性的個數(shù)
            for (int noM = 0; noM < numOfMethod; noM++) {
                System.out.print("方法作用域為: "
                        + getAccessFlag(bt[++index] + bt[++index],
                                accessMethodFlags));
                System.out.print("方法名字為: "
                        + constant[bt[++index] + bt[++index] - 1] + " 方法描述為 : "
                        + constant[bt[++index] + bt[++index] - 1]);
                int numOfAttrOfMethod = (int) (bt[++index] + bt[++index]); // 屬性的數(shù)量
                System.out.println(" 屬性數(shù)量為 : " + numOfAttrOfMethod);
                // 對于屬性應(yīng)該再進行判斷 大部分名字都是Code
                for (int noAOM = 0; noAOM < numOfAttrOfMethod; noAOM++) {
                    String methodAttribute = constant[bt[++index] + bt[++index]
                            - 1];
                    System.out.print("方法屬性分別為: " + methodAttribute);

                    int attributr_length = (int) (bt[++index] + bt[++index]
                            + bt[++index] + bt[++index]);// 取得屬性長度
                    System.out.print(" 屬性長度:" + attributr_length);
                    if ("Code".equals(methodAttribute)) {
                        index = dealWithCodeInfo(bt, index, constant, index
                                + attributr_length);
                    } else if ("Exceptions".equals(methodAttribute)) {
                        int exception_numbers = (bt[++index] + bt[++index]);
                        System.out.print(" 異常個數(shù):" + exception_numbers);
                        for (int i = 0; i < exception_numbers; i++)
                            System.out.println(" 異常為:"
                                    + constant[Integer
                                            .parseInt(constant[bt[++index]

                                            + bt[++index] - 1]) - 1]);
                    } else {
                        index += attributr_length;
                    }
                    System.out.println();
                }
                System.out.println();
            }
        }
        return index;
    }

    private static int dealWithCodeInfo(byte[] bt, int index,
            String[] constant, int end) {
        // index += 6;
        int maxstacks = bt[++index];
        maxstacks += bt[++index];
        System.out.print(" MAX STACK:" + maxstacks);
        int maxlocals = bt[++index];
        maxlocals += bt[++index];
        System.out.print(" MAX Locals:" + maxlocals);
        int code_length = (int) (bt[++index] + bt[++index] + bt[++index] + bt[++index]);
        System.out.println(" Code 長度:" + code_length);
        index += code_length;
        int exception_length = bt[++index];
        exception_length += bt[++index];
        System.out.println(" 異常個數(shù):" + exception_length);
        String[] c = new String[] { "start_pc", "end_pc", "handler_pc",
                "catch_type" };
        for (int i = 0; i < exception_length; i++) {

            for (int j = 0; j < 3; j++) {
                index += 2;
                System.out.print(" " + c[j] + ":" + bt[index]);
            }
            index += 2;
            System.out.println(" " + c[3] + ":"
                    + constant[Integer.parseInt(constant[bt[index] - 1]) - 1]);
        }
        int attribute_length = bt[++index];
        attribute_length += bt[++index];
        System.out.println(" 屬性個數(shù):" + attribute_length);
        for (int i = 0; i < attribute_length; i++) {
            String attribute_name = constant[bt[++index] + bt[++index] - 1];
            System.out.print("名字:" + attribute_name);
            int a_length = (int) bt[index + 4];
            index += 4;
            index += a_length;
            // System.out.println();
        }
        // System.out.println(index + "," + end);
        return index;
    }

    private static int dealWithFieldInfo(byte[] bt, int index, String[] constant) {
        int numOfField = bt[index];
        numOfField += bt[++index];
        // 打印字段
        if (numOfField == 0) {
            System.out.println(" - - - 無字段 - - -");
        } else {
            // 每個字段 前2個字節(jié)為 flag 之后2個字節(jié)為 name 之后2個字節(jié)為描述
            // 還有2個字節(jié)描述其他屬性的個數(shù)
            System.out.println("字段數(shù)為: " + numOfField);
            for (int noF = 0; noF < numOfField; noF++) {
                System.out.print("字段作用域為: "
                        + getAccessFlag(bt[++index] + bt[++index],
                                accessFieldFlags));
                System.out.print("字段名字為: "
                        + constant[bt[++index] + bt[++index] - 1] + " 字段描述為 : "
                        + constant[bt[++index] + bt[++index] - 1]);
                int numOfAttrOfFiled = (int) (bt[++index] + bt[++index]); // 屬性的數(shù)量
                System.out.println(" 屬性數(shù)量為 : " + numOfAttrOfFiled);
                // 每個屬性占有8個字節(jié)
                for (int noAoF = 0; noAoF < numOfAttrOfFiled; noAoF++) {
                    System.out.print("字段屬性分別為: "
                            + constant[bt[++index] + bt[++index] - 1]);
                    int attributr_length = (int) (bt[++index] + bt[++index]
                            + bt[++index] + bt[++index]);// 取得屬性長度
                    System.out.print(" 屬性長度:" + attributr_length);
                    int value = 0;
                    for (int i = 0; i < attributr_length; i++) {
                        value = value + bt[++index];
                    }
                    if (value < 100 && value > 0)
                        System.out.print(" 屬性內(nèi)容為: " + constant[value - 1]);
                    System.out.println();
                }
                System.out.println();
            }

        }
        return index + 1;
    }

    private static String getAccessFlag(int i, String[] accessFlags) {

        StringBuilder sb = new StringBuilder();
        int k = 0;
        while (i != 0) {
            int res = i & 1;
            if (res == 1)
                sb.append(accessFlags[k] + " ");
            i = i >> 1;
            k++;
        }
        return sb.toString();
    }

    private static int dealWithInterfaceInfo(byte[] bt, int index,
            String[] constant) {
        int numOfInterface = bt[index];
        numOfInterface += bt[++index];
        // 打印接口
        if (numOfInterface == 0) {
            System.out.println(" - - - 無接口 - - -");
        } else {
            // 每個接口占有2個字節(jié)
            System.out.println("接口數(shù)為: " + numOfInterface);
            for (int noI = 0; noI < numOfInterface; noI++) {
                System.out.println("接口分別為: "
                        + constant[bt[++index] + bt[++index] - 1]);
            }
        }
        return index + 1;
    }

    private static int dealWithClassInfo(byte[] bt, int index, String[] constant) {
        System.out.println((int) bt[index - 1]);
        System.out.println("當(dāng)前類為 : "
                + constant[Integer.parseInt(constant[bt[index] + bt[++index]
                        - 1]) - 1]);
        System.out.println(" 超  類 為 : "
                + constant[Integer.parseInt(constant[bt[++index] + bt[++index]
                        - 1]) - 1]);
        return index + 1;
    }

    private static int initConstPool(String[] constant, int[] constLen,
            byte[] bt) {
        int constNum = constant.length;
        int lastConst = 10; // 第一個表項的位置
        boolean isUTF8 = false; // 是否為utf8
        int line = 0;
        for (int i = 0; i < constNum; i++) {
            if (i > 0) {
                if (isUTF8)
                    lastConst += constLen[i - 1] + 3; // utf8 有兩個字節(jié)記錄長度
                else
                    lastConst += constLen[i - 1] + 1; // 之后每一個表項的起始位置
            }
            line = 0; // 記錄長度
            isUTF8 = recordConstLen(i, constLen, bt, lastConst);

            line = recordConsts(constant, lastConst, constLen, bt, isUTF8, i);

        }
        return line;

    }

    private static int recordConsts(String[] constant, int lastConst,
            int[] constLen, byte[] bt, boolean isUTF8, int k) {
        int i;
        if (isUTF8) {
            byte[] tmp = new byte[constLen[k]];
            for (i = lastConst + 3; i < lastConst + constLen[k] + 3; i++) {
                tmp[i - lastConst - 3] = bt[i];
            }
            constant[k] = new String(tmp);
            return i;
        }
        int temp = 0;
        for (i = lastConst + 1; i < lastConst + constLen[k] + 1; i++) {
            temp += (int) bt[i];
        }
        constant[k] = "" + temp;
        return i;
    }

    private static boolean recordConstLen(int i, int[] constLen, byte[] bt,
            int lastConst) {

        if ((int) bt[lastConst] != 1) {
            constLen[i] = map.get((int) bt[lastConst]);
            return false;
        } else {
            constLen[i] = ((int) bt[lastConst + 1]) + bt[lastConst + 2];
            return true;
        }

    }

    public static byte[] readFile(String file) throws IOException {
        InputStream is = new FileInputStream(file);
        int length = is.available();
        byte bt[] = new byte[length];
        is.read(bt);
        return bt;
    }

    public static void main(String[] agrs) throws IOException {
        String path = "F:/JVM/SimpleUser.class";
        byte[] bt = ClassReader.readFile(path);
        String hexData = ClassReader.format(bt);
        System.out.println(hexData);
    }
}
image.png

image.png

2.常用字節(jié)碼指令

知道了CODE里面的字節(jié)碼之后,就可以通過查表的方式,把字節(jié)碼指令翻譯出來,javap -verbose xxx.class 就有翻譯好的字節(jié)碼。
常用字節(jié)碼指令集
下圖是一個簡單的字節(jié)碼執(zhí)行過程

image.png

3.運行時棧幀結(jié)構(gòu)

棧幀(Stack Frame)是用于支持虛擬機進行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),它是虛擬機運行時數(shù)據(jù)區(qū)中的虛擬機棧(Virtual Machine Stack)的棧元素。棧幀存儲了方法的局部變量表、操作數(shù)棧、動態(tài)連接和方法返回地址等信息。每一個方法從調(diào)用開始至執(zhí)行完成的過程,都對應(yīng)著一個棧幀在虛擬機棧里面從入棧到出棧的過程。

每一個棧幀都包括了局部變量表、操作數(shù)棧、動態(tài)連接、方法返回地址和一些額外的附加信息。在編譯程序代碼的時候,棧幀中需要多大的局部變量表,多深的操作數(shù)棧都已經(jīng)完全確定了,并且寫入到方法表的 Code 屬性之中,因此一個棧幀需要分配多少內(nèi)存,不會受到程序運行期變量數(shù)據(jù)的影響,而僅僅取決于具體的虛擬機實現(xiàn)。

一個線程中的方法調(diào)用鏈可能會很長,很多方法都同時處于執(zhí)行狀態(tài)。對于執(zhí)行引擎來說,在活動線程中,只有位于棧頂?shù)臈攀怯行У?,稱為當(dāng)前棧幀(Current Stack Frame),與這個棧幀相關(guān)聯(lián)的方法稱為當(dāng)前方法(Current Method)。執(zhí)行引擎運行的所有字節(jié)碼指令都只針對當(dāng)前棧幀進行操作,在概念模型上,典型的棧幀結(jié)構(gòu)如圖所示。

image
    接下來詳細講解一下棧幀中的局部變量表、操作數(shù)棧、動態(tài)連接、方法返回地址等各個部分的作用和數(shù)據(jù)結(jié)構(gòu)。

局部變量表

局部變量表(Local Variable Table) 是一組變量值存儲空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量。在 Java 程序編譯為 Class 文件時,就在方法的 Code 屬性的 max_locals 數(shù)據(jù)項中確定了該方法所需要分配的局部變量表的最大容量。

局部變量表的容量以變量槽(Variable Slot,下稱 Slot)為最小單位,虛擬機規(guī)范中并沒有明確指明一個 Slot 應(yīng)占用的內(nèi)存空間大小,只是很有導(dǎo)向性地說到每個 Slot 都應(yīng)該能存放一個 boolean、byte、char、short、int、float、reference (注:Java 虛擬機規(guī)范中沒有明確規(guī)定 reference 類型的長度,它的長度與實際使用 32 還是 64 位虛擬機有關(guān),如果是 64 位虛擬機,還與是否開啟某些對象指針壓縮的優(yōu)化有關(guān),這里暫且只取 32 位虛擬機的 reference 長度)或 returnAddress 類型的數(shù)據(jù),這 8 種數(shù)據(jù)類型,都可以使用 32 位或更小的物理內(nèi)存來存放,但這種描述與明確指出 “每個 Slot 占用 32 位長度的內(nèi)存空間” 是有一些差別的,它允許 Slot 的長度可以隨著處理器、操作系統(tǒng)或虛擬機的不同而發(fā)送變化。只要保證即使在 64 位虛擬機中使用了 64 位的物理內(nèi)存空間去實現(xiàn)一個 Slot,虛擬機仍要使用對齊和補白的手段讓 Slot 在外觀上看起來與 32 位虛擬機中的一致。

既然前面提到了 Java 虛擬機的數(shù)據(jù)類型,在此再簡單介紹一下它們。一個 Slot 可以存放一個 32 位以內(nèi)的數(shù)據(jù)類型,Java 中占用 32 位以內(nèi)的數(shù)據(jù)類型有 boolean、byte、char、short、int、float、reference 和 returnAddress 8 種類型。前面 6 種不需要多加解釋,讀者可以按照 Java 語言中對應(yīng)數(shù)據(jù)類型的概念去理解它們(僅是這樣理解而已,Java 語言與 Java 虛擬機中的基本數(shù)據(jù)類型是存在本質(zhì)差別的),而第 7 種 reference 類型表示對一個對象實例的引用,虛擬機規(guī)范既沒有說明他的長度,也沒有明確指出這種引用應(yīng)有怎樣的結(jié)構(gòu)。但一般來說,虛擬機實現(xiàn)至少都應(yīng)當(dāng)能通過這個引用做到兩點,一是從此引用中直接或間接地查找到對象在 Java 堆中的數(shù)據(jù)存放的起始地址索引,二是此引用中直接或間接地查找到對象所屬數(shù)據(jù)類型在方法區(qū)中的存儲的類型信息,否則無法實現(xiàn) Java 語言規(guī)范中定義的語法約束約束。第 8 種即 returnAddress 類型目前已經(jīng)很少見了,它是為字節(jié)碼指令 jsr、jsr_w 和 ret 服務(wù)的,指向了一條字節(jié)碼指令的地址,很古老的 Java 虛擬機曾經(jīng)使用這幾條指令來實現(xiàn)異常處理,現(xiàn)在已經(jīng)由異常表代替。

對于 64 位的數(shù)據(jù)類型,虛擬機會以高位對齊的方式為其分配兩個連續(xù)的 Slot 空間。Java 語言中明確的(reference 類型則可能是 32 位也可能是 64 位)64 位的數(shù)據(jù)類型只有 long 和 double 兩種。值得一提的是,這里把 long 和 double 數(shù)據(jù)類型分割存儲的做法與 “l(fā)ong 和 double 非原子性協(xié)定” 中把一次 long 和 double 數(shù)據(jù)類型讀寫分割為兩次 32 位讀寫的做法有些類似,讀者閱讀到 Java 內(nèi)存模型時可以互相對比一下。不過,由于局部變量建立在線程的堆棧上,是線程私有的數(shù)據(jù),無論讀寫兩個連續(xù)的 Slot 是否為原子操作,都不會引起數(shù)據(jù)安全問題。

虛擬機通過索引定位的方式使用局部變量表,索引值的范圍是從 0 開始至局部變量表最大的 Slot 數(shù)量。如果訪問的是 32 位數(shù)據(jù)類型的變量,索引 n 就代表了使用第 n 個 Slot,如果是 64 位數(shù)據(jù)類型的變量,則說明會同時使用 n 和 n+1 兩個 Slot。對于兩個相鄰的共同存放一個 64 位數(shù)據(jù)的兩個 Slot,不允許采用任何方式單獨訪問其中的某一個,Java 虛擬機規(guī)范中明確要求了如果遇到進行這種操作的字節(jié)碼序列,虛擬機應(yīng)該在類加載的校驗階段拋出異常。

在方法執(zhí)行時,虛擬機是使用局部變量表完成參數(shù)值到參數(shù)變量列表的傳遞過程的,如果執(zhí)行的是實例方法(非 static 的方法),那局部變量表中第 0 位索引的 Slot 默認是用于傳遞方法所屬對象實例的引用,在方法中可以通過關(guān)鍵字 “this” 來訪問到這個隱含的參數(shù)。其余參數(shù)則按照參數(shù)表順序排列,占用從 1 開始的局部變量 Slot,參數(shù)表分配完畢后,再根據(jù)方法體內(nèi)部定義的變量順序和作用域分配其余的 Slot。

為了盡可能節(jié)省棧幀空間,局部變量中的 Slot 是可以重用的,方法體中定義的變量,其作用域并不一定會覆蓋整個方法體,如果當(dāng)前字節(jié)碼 PC 計數(shù)器的值已經(jīng)超出了某個變量的作用域,那這個變量對應(yīng)的 Slot 就可以交給其他變量使用。不過,這樣的設(shè)計除了節(jié)省棧幀空間以外,還會伴隨一些額外的副作用,例如,在某些情況下,Slot 的復(fù)用會直接影響到系統(tǒng)的垃圾收集行為,請看代碼清單 8-1 ~ 代碼清單 8-3 的 3 個演示。

代碼清單 8-1 局部變量表 Slot 復(fù)用對垃圾收集的影響之一

 public static void main(String[] args) {  
   { byte[] placeholder = new byte[64 * 1024 * 1024];  }
    System.gc();  
 }  
  1. [GC 66867K->66104K(124416K), 0.0010904 secs]
  2. [Full GC 66104K-><strong>66007K</strong>(124416K), 0.0089807 secs]

placeholder 能否被回收的根本原因是:局部變量中的 Slot 是否還存在關(guān)于 placeholder 數(shù)組對象的引用。第一次修改中,代碼雖然已經(jīng)離開了 placeholder 的作用域,但在此之后,沒有任何局部變量表的讀寫操作,placeholder 原本占用的 Slot 還沒有被其他變量所復(fù)用,所以作為 GC Roots 一部分的局部變量表仍然保持著對它的關(guān)聯(lián)。這種關(guān)聯(lián)沒有被及時打斷,在絕大部分情況下影響都很輕微。但如果遇到一個方法,其后面的代碼有一些耗時很長的操作,而前面又定義了占用了大量的內(nèi)存、實際上已經(jīng)不會再使用的變量,手動將其設(shè)置為 null 值(用來代替那句 int a=0,把變量對應(yīng)的局部變量表 Slot 清空)便不見得是一個絕對無意義的操作,這種操作可以作為一種在極特殊情形(對象占用內(nèi)存大、此方法的棧幀長時間不能被回收、方法調(diào)用次數(shù)達不到 JIT 的編譯條件)下的 “奇技” 來使用。Java 語言的一本著名書籍《Practical Java》中把 “不使用的對象應(yīng)手動賦值為 null” 作為一條推薦的編碼規(guī)則。

關(guān)于局部變量表,還有一點可能會對實際開發(fā)產(chǎn)生影響,就是局部變量不像前面介紹的類變量那樣存在 “準備階段”。通過之前的講解,我們已經(jīng)知道類變量有兩次賦初始值的過程,一次在準備階段,賦予系統(tǒng)初始化;另外一次在初始化階段,賦予程序員定義的初始值。因此,即使在初始化階段程序沒有為類變量賦值也沒有關(guān)系,類變量仍然具有一個確定的初始值。但局部變量就不一樣,如果一個局部變量定義了但沒有賦初始值是不能使用的,不要認為 Java 中任何情況下都存在諸如整型變量默認為 0,布爾型變量默認為 false 等這樣的默認值。

操作數(shù)棧

操作數(shù)棧(Operand Stack)也常稱為操作棧,它是一個后入先出(Last In First Out,LIFO)棧。同局部變量表一樣,操作數(shù)棧的最大深度也在編譯的時候?qū)懭氲?Code 屬性的 max_stacks 數(shù)據(jù)項中。操作數(shù)棧的每一個元素可以是任意的 Java 數(shù)據(jù)類型,包括 long 和 double。32 位數(shù)據(jù)類型所占的棧容量為 1,64 位數(shù)據(jù)類型所占的棧容量為 2。在方法執(zhí)行的任何時候,操作數(shù)棧的深度都不會超過在 max_stacks 數(shù)據(jù)項中設(shè)定的最大值。

當(dāng)一個方法剛剛開始執(zhí)行的時候,這個方法的操作數(shù)棧是空的,在方法的執(zhí)行過程中,會有各種字節(jié)碼指令往操作數(shù)棧中寫入和提取內(nèi)容,也就是出棧 / 入棧操作。例如,在做算術(shù)運算的時候是通過操作數(shù)棧來進行的,又或者再調(diào)用其他方法的時候是通過操作數(shù)棧來進行參數(shù)傳遞的。

另外,在概念模型中,兩個棧幀作為虛擬機棧的元素,是完全相互獨立的。但在大多虛擬機的實現(xiàn)里都會做一些優(yōu)化處理,令兩個棧幀出現(xiàn)一部分重疊。讓下面棧幀的部分操作數(shù)棧與上面棧幀的部分局部變量表重疊在一起,這樣在進行方法調(diào)用時就可以共用一部分數(shù)據(jù),無須進行額外的參數(shù)復(fù)制傳遞,重疊的過程如圖 8-2 所示。

image

Java 虛擬機的解釋執(zhí)行引擎稱為 “基于棧的執(zhí)行引擎”,其中所指的 “?!?就是操作數(shù)棧。

動態(tài)連接

每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支持方法調(diào)用過程中的動態(tài)連接(Dynamic Linking)。通過前面的講解,我們知道 Class 文件的常量池中存有大量的符號引用,字節(jié)碼中的方法調(diào)用指令就以常量池中指向方法的符號引用作為參數(shù)。這些符號引用一部分會在類加載階段或者第一次使用的時候就轉(zhuǎn)化為直接引用,這種轉(zhuǎn)化成為靜態(tài)解析。另外一部分將在每一次運行期間轉(zhuǎn)化為直接引用,這部分成為動態(tài)連接。

方法返回地址

當(dāng)一個方法開始執(zhí)行后,只有兩種方式可以退出這個方法。第一種方式是執(zhí)行引擎遇到任意一個方法返回的字節(jié)碼指令,這時候可能會有返回值傳遞給上層的方法調(diào)用者(調(diào)用當(dāng)前方法的方法稱為調(diào)用者),是否有返回值和返回值的類型將根據(jù)遇到何種方法返回指令來決定,這種退出方法的方式稱為正常完成出口(Normal Method Invocatino Completion)。

另外一種退出方式是,在方法執(zhí)行過程中遇到了異常,并且這個異常沒有在方法體內(nèi)得到處理,無論是 Java 虛擬機內(nèi)部產(chǎn)生的異常,還是代碼中使用 athrow 字節(jié)碼指令產(chǎn)生的異常,只要在本方法的異常表中沒有搜索到匹配的異常處理器,就會導(dǎo)致方法退出,這種退出方法的方式稱為異常完成出口(Abrupt Method Invocation Completion)。一個方法使用異常完成出口的方式退出,是不會給它的上層調(diào)用者產(chǎn)生任何返回值的。

無論采用何種退出方式,在方法退出之后,都需要返回到方法被調(diào)用的位置,程序才能繼續(xù)執(zhí)行,方法返回時可能需要在棧幀中保存一些信息,用來幫助恢復(fù)它的上層方法的執(zhí)行狀態(tài)。一般來說,方法正常退出時,調(diào)用者的 PC 計數(shù)器的值可以作為返回地址,棧幀中很可能會保存這個計數(shù)器值。而方法異常退出時,返回地址是要通過異常處理器表來確定的,棧幀中一般不會保存這部分信息。

方法退出的過程實際上就等同于把當(dāng)前棧幀出棧,因此退出時可能執(zhí)行的操作又:恢復(fù)上層方法的局部變量表和操作數(shù)棧,把返回值(如果有的話)壓入調(diào)用者棧幀的操作數(shù)棧中,調(diào)整 PC 計數(shù)器的值以指向方法調(diào)用指令后面的一條指令等。

附加信息

虛擬機規(guī)范允許具體的虛擬機實現(xiàn)增加一些規(guī)范里沒有描述的信息到棧幀之中,例如與調(diào)試相關(guān)的信息,這部分信息完全取決于具體的虛擬機實現(xiàn)。在實際開發(fā)中,一般會把動態(tài)連接、方法返回地址與其他附加信息全部歸為一類,稱為棧幀信息。

4.方法調(diào)用(解析,分派,動態(tài)語言支持)

請見我的另一篇文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容