1. 程式人生 > >JVM學習筆記——Class類檔案解讀

JVM學習筆記——Class類檔案解讀

簡述

Java原始碼通過編譯生成.class檔案位元組碼後再被JVM解釋轉化為目標機器程式碼,從而實現一次編寫到處,到處執行("Write Once,Run Anywhere")。位元組碼與平臺無關,而且並不是只有Java語言編譯為位元組碼檔案在虛擬機器上執行。

類檔案的結構

Class檔案是一組以8位位元組為基礎單位的二進位制流,各個資料專案嚴格按照順序緊湊地排列在Class檔案中。Class檔案只有兩種資料型別:無符號數和表。

無符號數屬於基本的資料型別,有u1, u2, u4, u8,分別代表1個位元組、2個位元組、4個位元組和8個位元組的無符號數

整個Class檔案就是一張表,由以下資料項構成:

型別 名稱 數量 u4 magic(魔數) 1 u2 minorversion(次版本號) 1 u2 majorversion(主版本號) 1 u2 constantpoolcount(常量池容量) 1 cpinfo constantpool(常量池) constantpoolcount-1 u2 accessflags(訪問標誌) 1 u2 thisclass(類索引) 1 u2 superclass(父類索引) 1 u2 interfacescount(介面容量) 1 u2 interfaces(介面) interfacescount u2 fieldscount(欄位容量) 1 fieldinfo fields(欄位) fields

count u2 methodscount(方法容量) 1 mehtodinfo methods(方法) methodcount u2 attributescount(屬性容量) 1 attribute attributes(屬性) attributes_count 小試牛刀

寫個簡單實體類,javac編譯後,檢視其位元組碼

原始碼

public class Person {
    private int age;

    public int getAge() {
        return age;
    }

    public static synchronized void work() {
        System.out.println("工作");
    }

    public static void main(String[] args) {
    }
}

十六進位制Class檔案

根據上述資料項表格我們按順序拆分

魔數

魔數站每個Class檔案的頭4個位元組,其作用未確定確定這個檔案是否為一個能被虛擬機器接受的Class檔案 示例中CA FE BA BE為魔數

版本號

魔數後面緊跟著版本號

00 00——次版本號

00 34——主版本號

根據如下:

十進位制版本號 主版本 jdk1.8 52 jdk1.7 51 jdk1.6 50 jdk1.5 49 jdk1.4 48 jdk1.3 47 jdk1.2 46 jdk1.1 45 十六進位制0034,對應十進位制52,對應jdk1.8版本

常量池

常量池可以理解為Class檔案之中的資源倉庫,它是Class檔案結構中與其他專案關聯最多的資料型別。常量池中主要存放兩大類常量:字面量和符號引用。

字面量——接近於Java中的常量概念,eg:final修飾、文字字串

符號引用——編譯原理概念:類和介面的全限定名、欄位的名稱和描述符、方法的名稱和描述符

常量池中的每一項常量都是一個表,每種常量都有自己的結構,14種常量含義:

型別 標誌 描述 CONSTANTutf8info 1 utf-8編碼的字串 CONSTANTIntegerinfo 3 整型字面量 CONSTANTFloatinfo 4 浮點型字面量 CONSTANTLonginfo 5 長整型字面量 CONSTANTDoubleinfo 6 雙精度浮點型字面量 CONSTANTClassinfo 7 類或者介面的符號引用 CONSTANTStringinfo 8 字串型字面量 CONSTANTFieldrefinfo 9 欄位的符號引用 CONSTANTMethodrefinfo 10 類中方法的符號引用 CONSTANTInterfaceMethoderfinfo 11 介面中方法的符號引用 CONSTANTNameAndTypeinfo 12 欄位或方法的部分符號引用 CONSTANTMethodHandleinfo 15 表示方法控制代碼 CONSTANTMethodTypeinfo 16 標識方法型別 CONSTANTInvokeDynamicinfo 18 表示一個動態方法呼叫點 0×0029轉十進位制為41,代表常量池中有40項常量(容量計數是從1而不是0開始。第0項常量空出來是表達“不引用任何一個常量池專案”)

0A即十進位制10,對應表中CONSTANTMethodrefinfo,其結構如下:

型別 名稱 描述 u1 tag 值為10 u2 index 指向宣告方法的類描述符CONSTANTClassinfo的索引項 u2 index 指向名稱及型別描述符CONSTANTNameAndTypeinfo的索引項 0×0007為常量池中第7項CONSTANTClassinfo,0×001A為第26項CONSTANTNameAndTypeinfo。按照《深入理解Java虛擬機器》第二版,172頁中表6-6順序解析得:加群617434785裡面有文中整理的知識點

00 29 //constantpoolcount(常量池容量) #1、0A 0007 001A //CONSTANTMethodrefinfo,#7,#26 #2、09 0006 001B //CONSTANTFieldrefinfo,#6,#27 #3、09 001C 001D //CONSTANTFieldrefinfo,#28,#29 #4、08 001E //CONSTANTStringinfo,#30 #5、0A 001F 0020 //CONSTANTMethodrefinfo,#31,#32 #6、07 0021 //CONSTANTClassinfo,#33 #7、07 0022 //CONSTANTClassinfo,#34 #8、01 0003 61 67 65 //CONSTANTUtf8info,3個位元組,age #9、01 0001 49 //CONSTANTUtf8info,1個位元組,I#10、01 0006 3C 69 6E 69 74 3E //CONSTANTUtf8info,6個位元組,#11、01 0003 28 29 56 //CONSTANTUtf8info,3個位元組,()V#12、01 0004 43 6F 64 65 //CONSTANTUtf8info,4個位元組,Code#13、01 000F 4C 69 6E 65 4E 75 6D 62 //CONSTANTUtf8info,15個位元組,LineNumberTable 65 72 54 61 62 6C 65 #14、01 0012 4C 6F 63 61 6C 56 61 72 //CONSTANTUtf8info,18個位元組,LocalVariableTable 69 61 62 6C 65 54 61 62 6C 65#15、01 0004 74 68 69 73 //CONSTANTUtf8info,4個位元組,this#16、01 0008 4C 50 65 72 73 6F 6E 3B //CONSTANTUtf8info,8個位元組,LPerson;#17、01 0006 67 65 74 41 67 65 //CONSTANTUtf8info,6個位元組,getAge#18、01 0003 28 29 49 //CONSTANTUtf8info,3個位元組,()I#19、01 0004 77 6F 72 6B //CONSTANTUtf8info,4個位元組,work#20、01 0004 6D 61 69 6E //CONSTANTUtf8info,4個位元組,main#21、01 0016 28 5B 4C 6A 61 76 61 2F //CONSTANTUtf8info,22個位元組,([Ljava/lang/String;)V 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56#22、01 0004 61 72 67 73 //CONSTANTUtf8info,4個位元組,args#23、01 0013 5B 4C 6A 61 76 61 2F 6C //CONSTANTUtf8info,19個位元組,[Ljava/lang/String; 61 6E 67 2F 53 74 72 69 6E 67 3B#24、01 000A 53 6F 75 72 63 65 46 69 //CONSTANTUtf8info,10個位元組, SourceFile 6C 65#25、01 000B 50 65 72 73 6F 6E 2E 6A //CONSTANTUtf8info,11個位元組, Person.java 61 76 61#26、0C 000A 000B //CONSTANTNameAndTypeinfo,#10,#11#27、0C 0008 0009 //CONSTANTNameAndTypeinfo,#8,#9#28、07 0023 //CONSTANTClassinfo,#35#29、0C 0024 0025 //CONSTANTNameAndTypeinfo,#36,#37#30、01 0006 E5 B7 A5 E4 BD 9C //CONSTANTUtf8info,6個位元組,工作#31、07 0026 //CONSTANTClassinfo,#38#32、0C 0027 0028 //CONSTANTNameAndTypeinfo,#39,#40#33、01 0006 50 65 72 73 6F 6E //CONSTANTUtf8info,6個位元組,Person#34、01 0010 6A 61 76 61 2F 6C 61 6E //CONSTANTUtf8info,16個位元組,java/lang/Object 67 2F 4F 62 6A 65 63 74#35、01 0010 6A 61 76 61 2F 6C 61 6E //CONSTANTUtf8info,16個位元組, java/lang/System 67 2F 53 79 73 74 65 6D#36、01 0003 6F 75 74 //CONSTANTUtf8info,3個位元組,out#37、01 0015 4C 6A 61 76 61 2F 69 6F //CONSTANTUtf8info,21個位元組, Ljava/io/PrintStream; 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B#38、01 0013 6A 61 76 2F 69 6F 2F 50 //CONSTANTUtf8info,19個位元組,java/io/PrintStream 72 69 6E 74 53 74 72 65 61 6D#39、01 0007 70 72 69 6E 74 6C 6E //CONSTANTUtf8info,7個位元組,println#40、01 0015 28 4C 6A 61 76 61 2F 6C //CONSTANTUtf8info,21個位元組, (Ljava/lang/String;)V 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56

也可以java -verbose分析Class檔案位元組碼,得到結果:

Constant pool: #1 = Methodref #7.#26 // java/lang/Object."":()V #2 = Fieldref #6.#27 // Person.age:I #3 = Fieldref #28.#29 // java/lang/System.out:Ljava/io/PrintStream; #4 = String #30 // 工作 #5 = Methodref #31.#32 // java/io/PrintStream.println:(Ljava/lang/String;)V #6 = Class #33 // Person #7 = Class #34 // java/lang/Object #8 = Utf8 age #9 = Utf8 I #10 = Utf8 #11 = Utf8 ()V #12 = Utf8 Code #13 = Utf8 LineNumberTable #14 = Utf8 LocalVariableTable #15 = Utf8 this #16 = Utf8 LPerson; #17 = Utf8 getAge #18 = Utf8 ()I #19 = Utf8 work #20 = Utf8 main #21 = Utf8 ([Ljava/lang/String;)V #22 = Utf8 args #23 = Utf8 [Ljava/lang/String; #24 = Utf8 SourceFile #25 = Utf8 Person.java #26 = NameAndType #10:#11 // "":()V #27 = NameAndType #8:#9 // age:I #28 = Class #35 // java/lang/System #29 = NameAndType #36:#37 // out:Ljava/io/PrintStream; #30 = Utf8 工作 #31 = Class #38 // java/io/PrintStream #32 = NameAndType #39:#40 // println:(Ljava/lang/String;)V #33 = Utf8 Person #34 = Utf8 java/lang/Object #35 = Utf8 java/lang/System #36 = Utf8 out #37 = Utf8 Ljava/io/PrintStream; #38 = Utf8 java/io/PrintStream #39 = Utf8 println #40 = Utf8 (Ljava/lang/String;)V

訪問標誌

在常量池之後緊接著兩個位元組代表訪問標誌,用於識別一些類或者介面層次的訪問資訊

具體的標誌位和含義如下:

名稱 標誌值 含義 ACCPUBLIC 0×0001 是否為public ACCFINAL 0x0010 是否為final ACCSUPER 0x0020 JDK 1.0.2之後編譯出來的類這個標誌都為真 ACCINTERFACE 0x0200 是否為一個介面 ACCABSTRACT 0x0400 是否為abstract型別 ACCSUPER 0x0020 JDK 1.0.2之後編譯出來的類這個標誌都為真 ACCSYNTHETIC 0x1000 標識這個類並非由使用者程式碼產生 ACCANNOTATION 0x2000 是否是註解 ACCENUM 0x4000 是否是列舉 沒有使用到的標誌位要求一律為0,本例accessflags的值為:ACCPUBLIC | ACCSUPER = 0x0021

類索引、父類索引與介面索引

類索引、父類索引與介面索引(指向常量池)都是u2型別的資料,除了java.lang.Object 之外所有的Java類都有父類,沒有實現介面計數器為0,本例:

0006 //thisclass Person 0007 //superclass java/lang/Object 0000 //沒有實現結構故0

欄位表集合

欄位表用於描述類和介面中宣告的變數。欄位包括類級變數和例項級變數,但是不包括方法中的變數。欄位資訊:欄位的作用域,public/private/protected 例項變數還是類變數,static 可變性,final 併發可見性, volatile 可否被序列化, transient,欄位資料型別(基本型別,物件,陣列),欄位名稱

欄位表結構:

型別 名稱 數量 u2 accessflags 1 u2 nameindex 1 u2 descriptorindex 1 u2 attributescount 1 attributeinfo attributes attributescount

對於本例:

0001 //fieldscount 欄位容量即1個欄位 0002 //訪問標誌 private 0008 //常量池第8項,即age 0009 //欄位描述符,常量池第9項,即I 0000 //attributecount

方法表集合

Class檔案儲存格式中對方法的描述與對欄位的描述幾乎採用完全一致的方式。

方法表結構:

型別 名稱 數量 u2 accessflags 1 u2 nameindex 1 u2 descriptorindex 1 u2 attributescount 1 attributeinfo attributes attributescount

對於本例:

0004 //方法容量,即4個方法:例項構造器、getAge()、work以及main方法 0001 //方法訪問標誌,public 000A //常量池第10項, 000B //方法描述常量池第11個,()V,沒返回值 0001 //attributecount 000C //常量池第12項,Code屬性表,存放方法裡的Java程式碼 0000002F //屬性表長度 47 … 47個位元組後0001 //方法訪問標誌,public0011 //常量池第17項,getAge0012 //方法描述常量池第18個,()I 返回int型0001 //attributecount000C //常量池第12項,Code屬性表,存放方法裡的Java程式碼0000002F //屬性表長度 47… 47個位元組後0029 // ACCPUBLIC,ACCSTATIC,ACCSYNCHRONIZED 0×0001|0×0008|0×00200013 //常量池第19項,work000B //方法描述常量池第11個,()V,沒返回值0001 //attributecount000C //常量池第12項,Code屬性表,存放方法裡的Java程式碼00000025 //屬性表長度 37…37個位元組後0009 //ACCPUBLIC, ACCSTATIC 0×0001|0×00080014 //常量池第20項,main0015 //方法描述常量池第21項,([Ljava/lang/String;)V String陣列形參,無返回型別方法0001 //attribute_count000C //常量池第12項,Code屬性表,存放方法裡的Java程式碼0000002B //屬性表長度 43

屬性表集合

對於本例:

構造方法:

000C //常量池第12項,Code屬性 0000002F //Code屬性表長度 47 0001 //maxstack 運算元棧深度最大值 1 0001 //maxlocals 區域性變數儲存 00000005 //codelength 位元組碼長度 2A B7 00 01 B1 //位元組碼指令 0000 //exceptiontablelength 0002 //attributescount 2個屬性 000D //常量池第13項, LineNumberTable屬性 00000006 //LineNumberTable屬性表長度 0001 //linenumbertablelength 0000 //startpc 位元組碼行號 0001 //linenumber Java原始碼行號 000E //常量池第14項,LocalVariableTable屬性 0000000C //attributelength 0001 //localvariabletablelength 0000 //startpc 這個區域性變數的生命週期開始的位元組碼偏移量 0005 //區域性變數作用範圍覆蓋的長度 000F //nameindex 區域性變數名稱 常量池第15項,this 0010 //descriptorindex 區域性變數描述 常量池第16項,LPerson; 0000 //這個區域性變數在棧幀區域性變量表中Slot的位置

getAge方法

000C //常量池第12項,Code屬性 0000002F //attributelength 0001 //maxstack 運算元棧深度最大值 1 0001 //maxlocals 區域性變數儲存 00000005 //codelength 位元組碼長度 2A B4 00 02 AC //位元組碼指令 0000 //exceptiontablelength 0002 //attributescount 2個屬性 000D //常量池第13項, LineNumberTable屬性 00000006 //LineNumberTable屬性表長度 0001 //linenumbertablelength 0000 //startpc 位元組碼行號 0006 //linenumber Java原始碼行號 000E //常量池第14項,LocalVariableTable屬性 0000000C //attributelength 0001 //localvariabletablelength 0000 //startpc 這個區域性變數的生命週期開始的位元組碼偏移量 0005 //區域性變數作用範圍覆蓋的長度 000F //nameindex 區域性變數名稱 常量池第15項,this 0010 //descriptor_index 區域性變數描述 常量池第16項,LPerson; 0000 //這個區域性變數在棧幀區域性變量表中Slot的位置

work方法

000C //常量池第12項,Code屬性 00000025 //attributelength 0002 //maxstack 運算元棧深度最大值 2 0000 //maxlocals 區域性變數儲存 00000009 //codelength 位元組碼長度 B2 00 03 12 04 B6 00 05 B1 //位元組碼指令 0000 //exceptiontablelength 0001 //attributescount 1個屬性 000D //常量池第13項, LineNumberTable屬性 0000000A //LineNumberTable屬性表長度 0002 //linenumbertablelength 2個linenumberinfo 0000 //startpc 位元組碼行號 000A //linenumber Java原始碼行號 0008 //startpc 位元組碼行號 000B //linenumber Java原始碼行號

main方法

000C //常量池第12項,Code屬性 0000002B //attributelength 0000 //maxstack 運算元棧深度最大值 0 0001 //maxlocals 區域性變數儲存 00000001 //codelength 位元組碼長度 B1 //位元組碼指令 0000 //exceptiontablelength 0002 //attributescount 2個屬性 000D //常量池第13項, LineNumberTable屬性 00000006 //LineNumberTable屬性表長度 0001 //linenumbertablelength 1個linenumberinfo 0000 //startpc 位元組碼行號 000F //linenumber Java原始碼行號 000E //常量池第14項,LocalVariableTable屬性 0000000C //attributelength 0001 //localvariabletablelength 0000 //startpc 這個區域性變數的生命週期開始的位元組碼偏移量 0001 //區域性變數作用範圍覆蓋的長度 0016 //nameindex 區域性變數名稱 常量池第22項,args 0017 //descriptor_index 區域性變數描述 常量池第23項,[Ljava/lang/String; 0000 //這個區域性變數在棧幀區域性變量表中Slot的位置

總結

本篇做了一個小小的嘗試,按照資料項表格一一解析,感興趣的同學可以讀下《深入理解Java虛擬機器》這本聖書。

如果想學習Java工程化、高效能及分散式、深入淺出。微服務、Spring,MyBatis,Netty原始碼分析的朋友可以加我的Java進階群:375989619,群裡有阿里大牛直播講解技術,以及Java大型網際網路技術的視訊免費分享給大家。