參考:
https://blog.csdn.net/IT_GJW/article/details/80447947
https://zhuanlan.zhihu.com/p/24789506
Class文件在JVM底層的實現
索引:
Class文件是一組以8位字節為基礎單位的二進制流,
各個數據項目按嚴格的順序緊湊的排列在Class文件中,
里面的信息主要描述以下信息:
1. 魔數和版本號
1.1. 魔數:0xCAFEBABE,確定這個文件是否為一個能被虛擬機接收的Class文件
1.2. Class文件的版本號:第5和第6個字節是次版本號(Minor Version),第7和第8個字節是主版本號(Major Version)。高版本的JDK能向下兼容以前版本的Class文件,但不能運行以后版本的Class文件。
2. 常量池
主要存放字面量(Literal)和符號引用(references)
- 字面量:文本字符串、final 類型的常量值 等
- 符號引用:
a. 類和接口的全限定名
b. 字段描述和描述符
c. 方法的名稱和描述
Java代碼在進行Javac編譯時,并不像C和C++那樣有“連接”的步驟,而是在虛擬機加載Class文件的時候進行動態連接。
也就是說,
在Class文件中不會保存各個方法、字段的最終內存布局信息,
當虛擬機運行時,需要從常量池獲得對應的符號引用,再在類創建時或運行時解析、翻譯到具體的內存地址之中。
JDK1.7中,總共有14種類型的常量,每種常量都是表類型的數據項。
這14種表都有一個共同的特點,就是表開始的第一位是一個u1類型的標志位,代表當前常量屬于哪種常量類型。
14種常量類型代表的具體含義見下表
類型 | 標志 | 描述 |
---|---|---|
Constant_Utf8_info | 1 | UTF-編碼的字符串 |
Constant_Integer_info | 3 | 整型字面量 |
Constant_Float_info | 4 | 浮點型字面量 |
Constant_Long_info | 5 | 長整型字面量 |
Constant_Double_info | 6 | 雙精度浮點型字面量 |
Constant_Class_info | 7 | 類或接口的符號引用 |
Constant_String_info | 8 | 字符串類型字面量 |
Constant_Fieldref_info | 9 | 字段的符號引用 |
Constant_Methodref_info | 10 | 類中方法的符號引用 |
Constant_InterfaceMethodref_info | 11 | 接口中方法的符號引用 |
Constant_NameAndType_info | 12 | 字段或方法的部分符號引用 |
Constant_MethodHandle_info | 15 | 表示方法句柄 |
Constant_MethodType_info | 16 | 標識方法類型 |
Constant_InvodeDynamic_info | 18 | 表示一個動態方法調用點 |
可以通過用命令javap -verbose TestClass.class命令查看class文件的字節碼內容,如
例:Constant_Methodref_info的結構
tag | index | index |
---|---|---|
u1 | u2 | u2 |
標志位,值如上表,10 | 指向生命方法的類描述符Constant_Class_info的索引項 | 指向名稱及類型描述符Constant_NameAndType_info的索引項 |
10 | 例:0x0004 | 例:0x000f |
例:Constant_Class_info的結構
tag | index |
---|---|
u1 | u2 |
標志位,值如上表,7 | 指向全限定名常量項的索引項 |
7 | 例:0x0011 |
例:Constant_Utf8_info的結構
tag | length | bytes |
---|---|---|
u1 | u2 | u1 |
標志位,值如上表,1 | UTF-8編碼的字符串占用的字節數 | 長度為length的UTF-8編碼的字符串 |
1 | 例:java/lang/Object |
3. 當前類的訪問標志:
常量池結束之后,緊接著的兩個字節代表訪問標志,這個標志用于識別一些類或者接口層次的訪問信息
a.這個Class是類還是接口
b.這個Class是否是public 等類型
c.這個Class是否是abstract ,是否被聲明為final 等標志
4. 類索引、父類索引和接口索引集合
a.類索引:確定這個類的全限定名
b.父類索引:確定父類的全限定名
c.接口索引集合:描述這個類實現了哪些接口,它是一組u2類型的數據的集合,集合中的第一項是接口計數器,表示索引表的容量。如果一個類沒有實現任何接口,則該計數器值為0。
5. 字段表集合(Fileds)
用于描述接口或者類中聲明的變量。
包括信息有
類型 | 名稱 | 數量 | 說明 |
---|---|---|---|
u2 | access_flags | 1 | 字段作用域(public,private等修飾符) 是實例變量還是類變量(static) 可變性 (final) 并發可見性(volatile) 可否被序列化(transient)等信息 |
u2 | name_index | 1 | 對常量池的引用 代表字段的簡單名稱 |
u2 | descriptor_index | 1 | 對常量池的引用 代表字段的描述符 描述字段的數據類型 |
u2 | attribute_count | 1 | 計數器 如果其值為0:字段沒有額外信息 如果其值不為0:則attribute_count后面會緊跟著attribute_count個attribute數據項。 |
attribute_info | attributes | attributes_count |
6. 方法表集合:
包括
類型 | 名稱 | 數量 | 說明 |
---|---|---|---|
u2 | access_flags | 1 | 訪問標志 |
u2 | name_index | 1 | 名稱索引 |
u2 | descriptor_index | 1 | 描述符索引 |
u2 | attributes_count | 1 | 屬性表集合 方法里的Java代碼經過編譯器編譯成字節碼指令后,存放在方法屬性表集合中一個名為“Code”的屬性表中。屬性表作為class文件格式中最具有擴展性的一種數據項目,將在隨后介紹。 |
attribute_info | attributes | attributes_count |
7. 其他:包括屬性表集合、Code 屬性(指令) 等。
屬性表(attribute_info)在前面的講解中已經出現過多次了,字段表、方法表都可以攜帶自己的屬性表集合,以用于描述某些場景專有的信息。
與Class文件中其他的數據項目要求嚴格的順序、長度和內容不同,
**屬性表集合的限制稍微寬松了一些,不再要求各個屬性表具有嚴格的順序。
為了能夠正確解析Class文件,《Java虛擬機規范(Java SE 7)》中,預定義了21項屬性表。下文將對一些常用的屬性表進行講解。
屬性名稱 | 使用位置 | 含義 |
---|---|---|
Code | 方法表 | Java代碼編譯成的字節碼指令 |
ConstantValue | 字段表 | final關鍵字定義的常量值 |
Deprecated | 類、方法表、字段表 | 被聲明為deprecated的方法和字段 |
Exceptions | 方法表 | 方法拋出的異常 |
EnclosingMethod | 類文件 | 僅當一個類為局部類或者匿名類時才能擁有這個屬性 這個屬性用于標識這個類所在的外圍方法 |
InnerClasses | 類文件 | 內部類列表 |
LineNumberTable | Code屬性 | Java源碼的行號與字節碼指令的對應關系 |
LineVariableTable | Code屬性 | 方法的局部變量描述 |
StackMapTable | Code屬性 | |
Signature | 類、方法表、字段表 | |
SourceFile | 類文件 | 記錄源文件名稱 |
SourceDebugExtension | 類文件 | |
Synthetic | 類、方法表、字段表 | |
LocalVariableTypeTable | 類 | |
RuntimeVisibleAnnotations | 類、方法表、字段表 | |
RuntimeInvisibleAnnotations | 類、方法表、字段表 | |
RuntimeVisibleParameter | 方法表 | |
RuntimeInvisibleParameter | 方法表 | |
AnnotationDefault | 方法表 | |
BootstrapMethod | 類文件 |
Code屬性
Code屬性存儲編譯后的字節碼指令,它出現在方法表的屬性集合中,
但并非所有方法都必須存在這個屬性,譬如抽象方法就不存在Code屬性。
Code屬性的結構如下表所示
類型 | 名稱 | 數量 | |
---|---|---|---|
u2 | attribute_name_index | 1 | 指向CONSTANT_Utf8_info型常量的索引 值固定為“Code” 代表該屬性的屬性名稱 attribute_length指示了屬性值的長度 |
u4 | attribute_length | 1 | |
u2 | max_stack | 1 |
操作數棧的最大值 在方法執行的任意時刻 操作數棧都不會超過這個深度 虛擬機運行時需要根據這個值來分配棧幀中操作棧深度 |
u2 | max_local | 1 | 局部變量表所需的存儲空間 它的單位是Slot |
u4 | code_length | 1 | 字節碼長度 |
u1 | code | code_length | 存儲字節碼指令的一系列字節流 每個指令就是一個u1類型的單字節 u1類型的取值范圍是0x00~0xFF 一共可以表達256條指令 |
u2 | exception_table_length | 1 | |
exception_info | exception_table | exception_table_length | |
u2 | attributes_count | 1 | |
attribute_info | attributes | attributes_count |
可以通過用命令javap -verbose TestClass.class命令查看一個class文件中方法的Code屬性,如
反射
Java反射機制就是在運行狀態中,
對于任意一個類,都能夠知道這個類的屬性和方法。
對于任意一個對象能夠調用它的任意一個屬性和方法。
這種動態獲取的信息和動態調用對象的方法的功能稱為Java語言的反射機制
反射機制就是通過Class類實現的。
在Java中,Object 類是所有類的根類,而Class類就是描述Java類的類。
在Java中,每一個class都有一個相應的Class對象,
在將Java源碼編譯成.class文件中就會生成一個Class對象,
Class對象表示這個類的類型信息,你也可以理解成Class是類的類型
注意:因為Class類也是類,所以Object也包括Class類
我們創建對象一般是通過new關鍵字創建,
但是new是靜態加載類,一旦找不到類就會編譯不通過。
但是通過反射機制創建對象一旦找不到類則拋出java.lang.ClassNotFoundException異常。
Class對象的常用方法:
Constructor[] getConstructors():返回此Class對象所表示的類的所有public構造方法
Method[] getMethods():返回此Class對象所表示的類的所有public方法
Method[] getDeclaredMethods():返回此Class對象所表示的類的所有方法,與方法的訪問級別無關
Field[] getFields():返回此Class對象所表示的類的所有public屬性
Field[] getDecalaredDields():返回此Class對象所表示的類的所有屬性,與屬性訪問級別無關
Object get(Object obj):得到引用類型屬性值
void set(Object obj,Object val):將obj對象的該屬性設置成val值。針對引用類型賦值
Object invoke(Object obj,Object args):調用類的方法,obj是執行該方法的對象,args是執行該方法時傳入該方法的參數
通過Class類得到指定的對象:
public interface Office {
/**
* 描述
*/
public void describe();
}
public class Word implements Office {
@Override
public void describe() {
System.out.println("大家好,我是Word");
}
}
public class Test {
public static void main(String[] string) throws ClassNotFoundException {
try {
@SuppressWarnings("rawtypes")
//傳入接口實現類的路徑
Class office = Class.forName("com.Word");
try {
//創建該類的對象
Office word = (Office)office.newInstance();
//調用方法
word.describe();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
通過對象得到Class類
public class Test {
@SuppressWarnings("rawtypes")
public static void main(String[] string) throws ClassNotFoundException {
Word word = new Word();
Class word1 = word.getClass(); // 通過對象的getClass()方法獲取Class
Class word2 = Word.class; // 通過類.class獲取Class
Class word3 = Class.forName("com.Word"); // 通過路徑獲取Class
System.out.println(word1 == word2);
System.out.println(word2 == word3);
}
}