Class文件結構
class文件是二進制文件,這里要介紹的是這個二級制文件的結構。
思考:一個java文件編譯成class文件,如果要描述一個java文件,需要哪些信息呢?
- 基本信息:類名、父類、實現哪些接口、方法個數、每個方法名稱和參數和訪問屬性、每個方法的內容、屬性個數、屬性名稱和類型和訪問屬性、構造方法。
- 編譯信息:編譯的JDK版本
Class文件結構參照表
- 常量池表長度 = constant_pool_count(常量個數) - 1, 原因是 常量池第0個位置被JVM占用了表示為null
Class文件示例分析
.java文件
public class TulingByteCode {private String userName;public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}
}
先將其編譯成class文件:javac TulingByteCode.java
再將.class文件反編譯:javap -v TulingByteCode.class
.class文件反編譯
Classfile /Users/xxxx/Documents/xxx/xxxx/app/test/src/test/java/com/xxxx/xxxxx/TulingByteCode.classLast modified 2025-8-14; size 450 bytesMD5 checksum 9e4f059c2aa6ec60f9c7f0093b58a2f6Compiled from "TulingByteCode.java"
public class com.xxxxx.xxxxxxx.TulingByteCodeminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref #4.#17 // java/lang/Object."<init>":()V#2 = Fieldref #3.#18 // com/xxxx/xxxxx/TulingByteCode.userName:Ljava/lang/String;#3 = Class #19 // com/xxxxx/xxxxxxx/TulingByteCode#4 = Class #20 // java/lang/Object#5 = Utf8 userName#6 = Utf8 Ljava/lang/String;#7 = Utf8 <init>#8 = Utf8 ()V#9 = Utf8 Code#10 = Utf8 LineNumberTable#11 = Utf8 getUserName#12 = Utf8 ()Ljava/lang/String;#13 = Utf8 setUserName#14 = Utf8 (Ljava/lang/String;)V#15 = Utf8 SourceFile#16 = Utf8 TulingByteCode.java#17 = NameAndType #7:#8 // "<init>":()V#18 = NameAndType #5:#6 // userName:Ljava/lang/String;#19 = Utf8 com/xxxx/xxxxxx/TulingByteCode#20 = Utf8 java/lang/Object
{public com.xxxxx.xxxxxx.TulingByteCode();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 7: 0public java.lang.String getUserName();descriptor: ()Ljava/lang/String;flags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: getfield #2 // Field userName:Ljava/lang/String;4: areturnLineNumberTable:line 11: 0public void setUserName(java.lang.String);descriptor: (Ljava/lang/String;)Vflags: ACC_PUBLICCode:stack=2, locals=2, args_size=20: aload_01: aload_12: putfield #2 // Field userName:Ljava/lang/String;5: returnLineNumberTable:line 15: 0line 16: 5
}
SourceFile: "TulingByteCode.java"
.class文件二進制
部分截圖如下:
.class文件分析
1. 常量池入口
第一行 00 09
常量池入口(常量個數),占用二個字節,表示常量池中的個數=00 19 (25)-1=24個, 為啥需要-1,因為常量池中的第0個位置被JVM占用了表示為null ,所以編譯出來的常量池索引是從1開始的.
對應的.class反編譯結果中,常量池有24個,索引從1開始。
2. 常量池類
JVM數據類型
????????JVM表示數據類型時,表示數據類型時有自己的規范。
????????基本參數類型和void類型都是用一個大寫的字符來表示,對象類型是通過一個大寫L加全類名表示,這么做的好處就是在保證jvm能讀懂class文件的情況下盡量的壓縮class文件體積.
基本數據類型表示:
- B---->byte
- C---->char
- D---->double
- F----->float
- I------>int
- J------>long
- S------>short
- Z------>boolean
- V------->void
對象類型:
- String------>Ljava/lang/String;(后面有一個分號)
對于數組類型: 每一個唯獨都是用一個前置 [ 來表示比如:
- int[] ------>[ I,
- String [][]------>[[Ljava.lang.String;
用描述符來描述方法,先參數列表,后返回值的格式,參數列表按照嚴格的順序放在()中。
比如源碼 String getUserInfoByIdAndName(int id,String name) 的方法描述符號(I,Ljava/lang/String;)Ljava/lang/String;
?常量池類型分類
u1,u2,u4,u8分別代表1個字節,2個字節,4個字節,8個字節的無符號數。
規范性的數據,暫時不全列出來了, 舉個栗子吧
常量 | 項目 | 類型 | 描述 |
CONSTANT_Utf8_info | tag | u1 | 值為1 |
length | u2 | UTF-8編碼的字符串占用的字節數 | |
bytes | u1 | 長度為length的UTF-8編碼的字符串 | |
CONSTANT_Methodref_info | tag | u1 | 值為10 |
class_index | u2 | 指向聲明方法的類描述符CONSTANT_Class_info的索引項 | |
name_and_type_index | u2 | 指向名稱和類型描述符CONSTANT_NameAndType 的索引項 |
分析常量池的第一個常量:
- class_index 00 04? ?: 二個字節表示的是是方法所在類 指向常量池的索引位置為#4,然后我們發現#4的常量類型是Class,也是符號引用類型,指向常量池#24的位置,而#24是的常量池類型是字面量值為:java/lang/Object
- name_and_type_index 00 15:二個字節表示是方法的描述符,指向常量池索引#21的位置,我們發現#21的常量類型是"NameAndType類型"屬于引用類型,指向常量池的#7 #8位置,#7常量類型是UTF-8類型屬于字面量值為:<init>? 構造方法, #8常量也是UTF-8類型的字面量值為:()V
所以常量池中的第一個常量是:java/lang/Object."<init>":()V
常量池可以看作java class類的一個資源倉庫(比如Java類定的方法和變量信息),我們后面的方法 類的信息的描述信息都是通過索引去常量池中獲取。
常量池中主要存放兩種常量,一種是字面量 ,一種是符號引用。
編譯的時候確認局部變量表的長度