Java字節碼常量池深度剖析與字節碼整體結構分解
常量池深度剖析:
在上一次【https://www.cnblogs.com/webor2006/p/9416831.html】中已經將常量池分析到了2/3了,接著把剩下的分析完,先回顧一下我們編譯的源文件為:
然後用javap -verbose查看一下編譯字節碼的信息,其中字符串相關的如下:
而對應用Hex Fiend來查看字符碼的二進制文件的位置如下:
另外在繼續分析之前再來回顧下常量的對應表,如下:
好下面開始,先來讀一個字節來看一下是什麽類型的常量:
查表可以看到是屬於這個常量:
接著2個字節表示字符串的長度,所以往下數二個字節:
長度為4,則下往下數4個字節則為常量的內容:
用javap -verbose來確認一下是否也是它:
接下來繼續讀一個字節:
又是同樣的常量類型,所以直接再讀二個字節來看一下字符串的長度是多少:
長度為3,則往後再數3個字節:
看一下javap -verbose:
實際上"getA()I"就可以確認其方法名為getA,無參,並且返回值為整型,就可以完全的對應的源程序中的方法了。
接下來繼續往下,讀一個字節:
同樣的類型,不多說,直接往下再看兩個字節來決定字符串的長度:
占四個字節,於是乎往後再數四個字節:
對一下javap -verbose:
繼續往下,讀一個字節:
再數2個字節:
往下數四個字節:
對應於javap -verbose:
而同樣的“setA(I)V”,表示方法名為setA,方法的參數為整型,無返回值,這樣又可以定位到具體的唯一的方法了。
繼續往下:
往下數10個字節:
對應javap -verbose:
代表源文件,再往下讀:
長度為12,往下數12個字節:
而這兩個信息描述了當前字節碼文件是由哪個源文件編譯出來的,所以這也是為啥在執行javap命令時有如下一個信息:
繼續往下走:
此時不再是01類型的常量了,而是12,所以具體它代表什麽類型還得查表,如下:
其第二項表示名稱的索引,而第三項為描述的索引,所以往下讀4個字節:
看一下javap -verbose所顯示的:
其中方法名稱為<init>表示是構造方法,而()V表示該構造方法不帶參數沒的返回值,也就是默認構造方法。
接下來繼續:
又是同樣的常量,所以直接往後數四個字節:
對應javap -verbose:
這個信息表示成員變量a,如下:
繼續往下看:
字符串常量,對它的分析已經了如指掌了,往後數兩個字節來看下字符串的長度:
長度為24,則往後數24個字節:
對應javap -verbose:
表示類的全局限定名,註意反應到字節碼文件來說全局限定名都是以“/”分隔的,而不像我們看到的包名那樣以“.”分隔的,繼續往下:
再往下數兩個字節:
16,則往下數16個字節:
對應javap -verbose:
表示當前類的父類的完全限制名,到此,常量池就全部分析完了~所以總結一下,對於一個類常量池的大小是不定的,那JVM如何在字體碼文件中來知道常量池在哪結束呢?首先字符碼能知道常量池的總大小,如下:
為24個,但是由於第一個為備用的,所以總常量池的大小為23,而每個常量第一個字節都是什麽類型的常量,然後不同的常量其往下讀幾個字節都是確定的,所以這樣就可以知道讀到哪常量池就結束了。
字節碼整體結構分解:
上面已經將整個常量池都已經分析完了,那之後還有那麽多字節:
對應javap -verbose:
Classfile /Users/xiongwei/Documents/workspace/IntelliJSpace/jvm_lectue/out/production/classes/com/jvm/bytecode/MyTest1.class Last modified Aug 10, 2018; size 479 bytes MD5 checksum 4616561f95c24d6b04ea48a360437b8d Compiled from "MyTest1.java" public class com.jvm.bytecode.MyTest1 minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #4.#20 // java/lang/Object."<init>":()V #2 = Fieldref #3.#21 // com/jvm/bytecode/MyTest1.a:I #3 = Class #22 // com/jvm/bytecode/MyTest1 #4 = Class #23 // java/lang/Object #5 = Utf8 a #6 = Utf8 I #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lcom/jvm/bytecode/MyTest1; #14 = Utf8 getA #15 = Utf8 ()I #16 = Utf8 setA #17 = Utf8 (I)V #18 = Utf8 SourceFile #19 = Utf8 MyTest1.java #20 = NameAndType #7:#8 // "<init>":()V #21 = NameAndType #5:#6 // a:I #22 = Utf8 com/jvm/bytecode/MyTest1 #23 = Utf8 java/lang/Object { public com.jvm.bytecode.MyTest1(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: iconst_1 6: putfield #2 // Field a:I 9: return LineNumberTable: line 3: 0 line 4: 4 LocalVariableTable: Start Length Slot Name Signature 0 10 0 this Lcom/jvm/bytecode/MyTest1; public int getA(); descriptor: ()I flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #2 // Field a:I 4: ireturn LineNumberTable: line 7: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/jvm/bytecode/MyTest1; public void setA(int); descriptor: (I)V flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=2 0: aload_0 1: iload_1 2: putfield #2 // Field a:I 5: return LineNumberTable: line 11: 0 line 12: 5 LocalVariableTable: Start Length Slot Name Signature 0 6 0 this Lcom/jvm/bytecode/MyTest1; 0 6 1 a I } SourceFile: "MyTest1.java"
目前還分析不了,因為還缺少知識理論,所以先來補一補知識,先來對字節碼的整體結構有一個了解,先來看張圖:
目前已經學習了前三個結構,如下:
接著來了解新的字節結構,接下來是“Access Flags”,表示訪問修飾符:
如:public、public static、public abstract、private、protected等。
接著往下表示當前類的名字,如下:
再往下表示父類的名字:
接下來表示接口相關的信息:
其中可以發現一個細節:
父類的字節數是確定的,而接口是不確實的,這也跟java的單繼承多實現的特性吻和。
繼續往下則是字段相關的信息:
接下來由是類的方法相關的一些信息:
這個就比較復雜了,因為方法裏面有執行代碼,在未來會學習到。
最後則表示當前類的一些附加的屬性:
因為JVM在編譯時會增加一些特定的一些屬性信息。
Java字節碼常量池深度剖析與字節碼整體結構分解