(8) CONSTANT_Class_infojava
常量池中的一個CONSTANT_Class_info, 能夠看作是CONSTANT_Class數據類型的一個實例。 他是對類或者接口的符號引用。 它描述的能夠是當前類型的信息, 也能夠描述對當前類的引用, 還能夠描述對其餘類的引用。 也就是說, 若是訪問了一個類字段, 或者調用了一個類的方法, 對這些字段或方法的符號引用, 必須包含它們所在的類型的信息, CONSTANT_Class_info就是對字段或方法符號引用中類型信息的描述。 數組
CONSTANT_Class_info的第一個字節是tag, 值爲7, 也就是說, 當虛擬機訪問到一個常量池中的數據項, 若是發現它的tag值爲7, 就能夠判斷這是一個CONSTANT_Class_info 。 tag下面的兩個字節是一個叫作name_index的索引值, 它指向一個CONSTANT_Utf8_info, 這個CONSTANT_Utf8_info中存儲了CONSTANT_Class_info要描述的類型的全限定名。 全限定名的概念在前面的博文 深刻理解Java Class文件格式(二) 中將結果, 不熟悉的同窗能夠先閱讀這篇文章。 佈局
此外要說明的是, java中數組變量也是對象, 那麼數組也就有相應的類型, 而且數組的類型也是使用CONSTANT_Class_info描述的, 而且數組類型和普通類型的描述有些區別。 普通類型的CONSTANT_Class_info中存儲的是全限定名, 而數組類型對應的CONSTANT_Class_info中存儲的是數組類型相對應的描述符字符串。 舉例說明:this
與Object類型對應的CONSTANT_Class_info中存儲的是: java/lang/Object
與Object[]類型對應的CONSTANT_Class_info中存儲的是: [Ljava/lang/Object; spa
下面看CONSTANT_Class_info的存儲佈局:3d
例如, 若是在一個類中引用了System這個類, 那麼就會在這個類的常量池中出現如下信息:code
(9) CONSTANT_Fieldref_info對象
常量池中的一個CONSTANT_Fieldref_info, 能夠看作是CONSTANT_Field數據類型的一個實例。 該數據項表示對一個字段的符號引用, 能夠是對本類中的字段的符號引用, 也能夠是對其餘類中的字段的符號引用, 能夠是對成員變量字段的符號引用, 也能夠是對靜態變量的符號引用, 其中ref三個字母就是reference的簡寫。 以前的文章中, 「符號引用」這個名詞出現了不少次, 可能有的同窗一直不是很明白, 等介紹完CONSTANT_Fieldref_info, 就能夠很清晰的瞭解什麼是符號引用。 下面分析CONSTANT_Fieldref_info中的內容都存放了什麼信息。 blog
和其餘類型的常量池數據項同樣, 它的第一個字節也必然是tag, 它的tag值爲9 。 也就是說, 當虛擬機訪問到一個常量池中的一項數據, 若是發現這個數據的tag值爲9, 就能夠肯定這個被訪問的數據項是一個CONSTANT_Fieldref_info, 而且知道這個數據項表示對一個字段的符號引用。 索引
tag值下面的兩個字節是一個叫作class_index的索引值, 它指向一個CONSTANT_Class_info數據項, 這個數據項表示被引用的字段所在的類型, 包括接口。 因此說, CONSTANT_Class_info能夠做爲字段符號引用的一部分。
class_index如下的兩個字節是一個叫作name_and_type_index的索引, 它指向一個CONSTANT_NameAndType_info, 這個CONSTANT_NameAndType_info前面的博客中已經解釋過了, 不明白的朋友能夠先看前面的博客:深刻理解Java Class文件格式(三) 。 這個CONSTANT_NameAndType_info描述的是被引用的字段的名稱和描述符。 咱們在前面的博客中也提到過, CONSTANT_NameAndType_info能夠做爲字段符號引用的一部分。
到此, 咱們能夠說, CONSTANT_Fieldref_info就是對一個字段的符號引用, 這個符號引用包括兩部分, 一部分是該字段所在的類, 另外一部分是該字段的字段名和描述符。 這就是所謂的 「對字段的符號引用」 。
下面結合實際代碼來講明, 代碼以下:
package com.jg.zhang; public class TestInt { int a = 10; void print(){ System.out.println(a); } }
在print方法中, 引用了本類中的字段a。 代碼很簡單, 咱們一眼就能夠看到print方法中是如何引用本類中定義的字段a的。 那麼在class文件中, 對字段a的引用是如何描述的呢? 下面咱們將這段代碼使用javap反編譯, 給出簡化後的反編譯結果:
Constant pool:
#1 = Class #2 // com/jg/zhang/TestInt
#2 = Utf8 com/jg/zhang/TestInt
......
#5 = Utf8 a
#6 = Utf8 I
......
#12 = Fieldref #1.#13 // com/jg/zhang/TestInt.a:I
#13 = NameAndType #5:#6 // a:I
......
{
void print();
flags:
Code:
stack=2, locals=1, args_size=1
0: getstatic #19 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #12 // Field a:I
7: invokevirtual #25 // Method java/io/PrintStream.println:(I)V
10: return
}
能夠看到, print方法的位置爲4的字節碼指令getfield引用了索引爲12的常量池數據項, 常量池中索引爲12的數據項是一個CONSTANT_Fieldref_info, 這個CONSTANT_Fieldref_info又引用了索引爲1和13的兩個數據項, 索引爲1的數據項是一個CONSTANT_Class_info, 這個CONSTANT_Class_info數據項又引用了索引爲2的數據項, 索引爲2的數據項是一個CONSTANT_Utf8_info , 他存儲了字段a所在的類的全限定名com/jg/zhang/TestInt 。 而CONSTANT_Fieldref_info所引用的索引爲13的數據項是一個CONSTANT_NameAndType_info, 它又引用了兩個數據項, 分別爲第5項和第6項, 這是兩個CONSTANT_Utf8_info , 分別存儲了字段a的字段名a, 和字段a的描述符I 。
下面給出內存佈局圖, 這個圖中涉及的東西有點多, 由於CONSTANT_Fieldref_info引用了CONSTANT_Class_info和CONSTANT_NameAndType_info, CONSTANT_Class_info又引用了一個CONSTANT_Utf8_info , 而CONSTANT_NameAndType_info又引用了兩個CONSTANT_Utf8_info 。
(10) CONSTANT_Methodref_info
常量池中的一個CONSTANT_Methodref_info, 能夠看作是CONSTANT_Methodref數據類型的一個實例。 該數據項表示對一個類中方法的符號引用, 能夠是對本類中的方法的符號引用, 也能夠是對其餘類中的方法的符號引用, 能夠是對成員方法字段的符號引用, 也能夠是對靜態方法的符號引用,可是不會是對接口中的方法的符號引用。 其中ref三個字母就是reference的簡寫。 在上一小節中介紹了CONSTANT_Fieldref_info, 它是對字段的符號引用, 本節中介紹的CONSTANT_Methodref_info和CONSTANT_Fieldref_info很類似。既然是符號「引用」, 那麼只有在原文件中調用了一個方法, 常量池中才有和這個被調用方法的相對應的符號引用, 即存在一個CONSTANT_Methodref_info。 若是隻是在類中定義了一個方法, 可是沒調用它, 則不會在常量池中出現和這個方法對應的CONSTANT_Methodref_info 。
和其餘類型的常量池數據項同樣, 它的第一個字節也必然是tag, 它的tag值爲10 。 也就是說, 當虛擬機訪問到一個常量池中的一項數據, 若是發現這個數據的tag值爲10, 就能夠肯定這個被訪問的數據項是一個CONSTANT_Methodref_info, 而且知道這個數據項表示對一個方法的符號引用。
tag值下面的兩個字節是一個叫作class_index的索引值, 它指向一個CONSTANT_Class_info數據項, 這個數據項表示被引用的方法所在的類型。 因此說, CONSTANT_Class_info能夠做爲方法符號引用的一部分。
class_index如下的兩個字節是一個叫作name_and_type_index的索引, 它指向一個CONSTANT_NameAndType_info, 這個CONSTANT_NameAndType_info前面的博客中已經解釋過了, 不明白的朋友能夠先看前面的博客:深刻理解Java Class文件格式(三) 。 這個CONSTANT_NameAndType_info描述的是被引用的方法的名稱和描述符。 咱們在前面的博客中也提到過, CONSTANT_NameAndType_info能夠做爲方法符號引用的一部分。
到此, 咱們能夠知道, CONSTANT_Methodref_info就是對一個字段的符號引用, 這個符號引用包括兩部分, 一部分是該方法所在的類, 另外一部分是該方法的方法名和描述符。 這就是所謂的 「對方法的符號引用」 。下面結合實際代碼來講明, 代碼以下:
package com.jg.zhang; public class Programer { Computer computer; public Programer(Computer computer){ this.computer = computer; } public void doWork(){ computer.calculate(); } } package com.jg.zhang; public class Computer { public void calculate() { System.out.println("working..."); } }
上面的代碼包括兩個類, 其中Programer類引用了Computer類, 在Programer類的doWork方法中引用(調用)了Computer類的calculate方法。源碼中對一個方法的描述形式咱們再熟悉不過了, 如今咱們就反編譯Programer, 看看Programer中對Computer的doWork方法的引用, 在class文件中是如何描述的。
下面給出Programer的反編譯結果, 其中省去了一些不相關的信息:
Constant pool:
.........
#12 = Utf8 ()V
#20 = Methodref #21.#23 // com/jg/zhang/Computer.calculate:()V
#21 = Class #22 // com/jg/zhang/Computer
#22 = Utf8 com/jg/zhang/Computer
#23 = NameAndType #24:#12 // calculate:()V
#24 = Utf8 calculate
{
com.jg.zhang.Computer computer;
flags:
.........
public void doWork();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #13 // Field computer:Lcom/jg/zhang/Computer;
4: invokevirtual #20 // Method com/jg/zhang/Computer.calculate:()V
7: return
}
能夠看到, doWork方法的位置爲4的字節碼指令invokevirtual引用了索引爲20的常量池數據項, 常量池中索引爲20的數據項是一個CONSTANT_Methodref_info, 這個CONSTANT_Methodref_info又引用了索引爲21和23的兩個數據項, 索引爲21的數據項是一個CONSTANT_Class_info, 這個CONSTANT_Class_info數據項又引用了索引爲22的數據項, 索引爲22的數據項是一個CONSTANT_Utf8_info , 他存儲了被引用的Computer類中的calculate方法所在的類的全限定名com/jg/zhang/Computer 。 而CONSTANT_Methodref_info所引用的索引爲23的數據項是一個CONSTANT_NameAndType_info, 它又引用了兩個數據項, 分別爲第24項和第12項, 這是兩個CONSTANT_Utf8_info , 分別存儲了被引用的方法calculate的方法名calculate, 和該方法的描述符()V 。
下面給出內存佈局圖, 這個圖中涉及的東西一樣有點多, 由於CONSTANT_Methodref_info引用了CONSTANT_Class_info和CONSTANT_NameAndType_info, CONSTANT_Class_info又引用了一個CONSTANT_Utf8_info , 而CONSTANT_NameAndType_info又引用了兩個CONSTANT_Utf8_info 。
(11) CONSTANT_InterfaceMethodref_info
常量池中的一個CONSTANT_InterfaceMethodref_info, 能夠看作是CONSTANT_InterfaceMethodref數據類型的一個實例。 該數據項表示對一個接口方法的符號引用, 不能是對類中的方法的符號引用。 其中ref三個字母就是reference的簡寫。 在上一小節中介紹了CONSTANT_Methodref_info, 它是對類中的方法的符號引用, 本節中介紹的CONSTANT_InterfaceMethodref和CONSTANT_Methodref_info很類似。既然是符號「引用」, 那麼只有在原文件中調用了一個接口中的方法, 常量池中才有和這個被調用方法的相對應的符號引用, 即存在一個CONSTANT_InterfaceMethodref。 若是隻是在接口中定義了一個方法, 可是沒調用它, 則不會在常量池中出現和這個方法對應的CONSTANT_InterfaceMethodref 。
和其餘類型的常量池數據項同樣, 它的第一個字節也必然是tag, 它的tag值爲11 。 也就是說, 當虛擬機訪問到一個常量池中的一項數據, 若是發現這個數據的tag值爲11, 就能夠肯定這個被訪問的數據項是一個CONSTANT_InterfaceMethodref, 而且知道這個數據項表示對一個接口中的方法的符號引用。
tag值下面的兩個字節是一個叫作class_index的索引值, 它指向一個CONSTANT_Class_info數據項, 這個數據項表示被引用的方法所在的接口。 因此說, CONSTANT_Class_info能夠做爲方法符號引用的一部分。
class_index如下的兩個字節是一個叫作name_and_type_index的索引, 它指向一個CONSTANT_NameAndType_info, 這個CONSTANT_NameAndType_info前面的博客中已經解釋過了, 不明白的朋友能夠先看前面的博客:深刻理解Java Class文件格式(三) 。 這個CONSTANT_NameAndType_info描述的是被引用的方法的名稱和描述符。 咱們在前面的博客中也提到過, CONSTANT_NameAndType_info能夠做爲方法符號引用的一部分。
到此, 咱們能夠知道, CONSTANT_InterfaceMethodref就是對一個接口中的方法的符號引用, 這個符號引用包括兩部分, 一部分是該方法所在的接口, 另外一部分是該方法的方法名和描述符。 這就是所謂的 「對接口中的方法的符號引用」 。
下面結合實際代碼來講明, 代碼以下:
package com.jg.zhang; public class Plane { IFlyable flyable; void flyToSky(){ flyable.fly(); } } package com.jg.zhang; public interface IFlyable { void fly(); }
在上面的代碼中, 定義可一個類Plane, 在這個類中有一個IFlyable接口類型的字段flyable, 而後在Plane的flyToSky方法中調用了IFlyable中的fly方法。 這就是源代碼中對一個接口中的方法的引用方式, 下面咱們反編譯Plane, 看看在class文件層面, 對一個接口中的方法的引用是如何描述的。
下面給出反編譯結果, 爲了簡潔期間, 省略了一些不相關的內容:
Constant pool:
.........
#8 = Utf8 ()V
#19 = InterfaceMethodref #20.#22 // com/jg/zhang/IFlyable.fly:()V
#20 = Class #21 // com/jg/zhang/IFlyable
#21 = Utf8 com/jg/zhang/IFlyable
#22 = NameAndType #23:#8 // fly:()V
#23 = Utf8 fly
{
.........
com.jg.zhang.IFlyable flyable;
flags:
.........
void flyToSky();
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #17 // Field flyable:Lcom/jg/zhang/IFlyable;
4: invokeinterface #19, 1 // InterfaceMethod com/jg/zhang/IFlyable.fly:()V
9: return
}
能夠看到, flyToSky方法的位置爲4的字節碼指令invokeinterface引用了索引爲19的常量池數據項, 常量池中索引爲19的數據項是一個CONSTANT_InterfaceMethodref_info, 這個CONSTANT_InterfaceMethodref_info又引用了索引爲20和22的兩個數據項, 索引爲20的數據項是一個CONSTANT_Class_info, 這個CONSTANT_Class_info數據項又引用了索引爲21的數據項, 索引爲21的數據項是一個CONSTANT_Utf8_info , 他存儲了被引用的方法fly所在的接口的全限定名com/jg/zhang/IFlyable 。 而CONSTANT_InterfaceMethodref_info所引用的索引爲22的數據項是一個CONSTANT_NameAndType_info, 它又引用了兩個數據項, 分別爲第23項和第8項, 這是兩個CONSTANT_Utf8_info , 分別存儲了被引用的方法fly的方法名fly, 和該方法的描述符()V 。
下面給出內存佈局圖, 這個圖中涉及的東西一樣有點多, 由於CONSTANT_InterfaceMethodref_info引用了CONSTANT_Class_info和CONSTANT_NameAndType_info, CONSTANT_Class_info又引用了一個CONSTANT_Utf8_info , 而CONSTANT_NameAndType_info又引用了兩個CONSTANT_Utf8_info 。
總結
到此爲止, class文件中的常量池部分就已經講解完了。 進行一下總結。對於深刻理解Java和JVM , 理解class文件的格式相當重要, 而在class文件中, 常量池是一項很是重要的信息。 常量池中有11種數據項, 這個11種數據項存儲了各類信息, 包括常量字符串, 類的信息, 方法的符號引用, 字段的符號引用等等。 常量池中的數據項經過索引來訪問, 訪問形式相似於數組。 常量池中的各個數據項以前會經過索引相互引用, class文件的其餘地方也會引用常量池中的數據項 , 如方法的字節碼指令。
在下面的文章中, 會繼續介紹class文件中, 位於常量池如下的其餘信息。 這些信息包括:對本類的描述, 對父類的描述, 對實現的接口的描述, 本類中聲明的字段的描述, 本類彙總定義的方法的描述,還有各類屬性。