深入理解 JVM Class文件格式(五)
(8) CONSTANT_Class_info
常量池中的一個(gè)CONSTANT_Class_info, 可以看做是CONSTANT_Class數(shù)據(jù)類型的一個(gè)實(shí)例。 他是對(duì)類或者接口的符號(hào)引用。 它描述的可以是當(dāng)前類型的信息, 也可以描述對(duì)當(dāng)前類的引用, 還可以描述對(duì)其他類的引用。 也就是說(shuō), 如果訪問(wèn)了一個(gè)類字段, 或者調(diào)用了一個(gè)類的方法, 對(duì)這些字段或方法的符號(hào)引用, 必須包含它們所在的類型的信息, CONSTANT_Class_info就是對(duì)字段或方法符號(hào)引用中類型信息的描述。
CONSTANT_Class_info的第一個(gè)字節(jié)是tag, 值為7, 也就是說(shuō), 當(dāng)虛擬機(jī)訪問(wèn)到一個(gè)常量池中的數(shù)據(jù)項(xiàng), 如果發(fā)現(xiàn)它的tag值為7, 就可以判斷這是一個(gè)CONSTANT_Class_info 。 tag下面的兩個(gè)字節(jié)是一個(gè)叫做name_index的索引值, 它指向一個(gè)CONSTANT_Utf8_info, 這個(gè)CONSTANT_Utf8_info中存儲(chǔ)了CONSTANT_Class_info要描述的類型的全限定名。 全限定名的概念在前面的博文 深入理解 JVM Class文件格式(二) 中描述過(guò), 不熟悉的同學(xué)可以先閱讀這篇文章。
此外要說(shuō)明的是, java中數(shù)組變量也是對(duì)象, 那么數(shù)組也就有相應(yīng)的類型, 并且數(shù)組的類型也是使用CONSTANT_Class_info描述的, 并且數(shù)組類型和普通類型的描述有些區(qū)別。 普通類型的CONSTANT_Class_info中存儲(chǔ)的是全限定名, 而數(shù)組類型對(duì)應(yīng)的CONSTANT_Class_info中存儲(chǔ)的是數(shù)組類型相對(duì)應(yīng)的描述符字符串。 舉例說(shuō)明:
與Object類型對(duì)應(yīng)的CONSTANT_Class_info中存儲(chǔ)的是: java/lang/Object
 與Object[]類型對(duì)應(yīng)的CONSTANT_Class_info中存儲(chǔ)的是: [Ljava/lang/Object;
下面看CONSTANT_Class_info的存儲(chǔ)布局:
例如, 如果在一個(gè)類中引用了System這個(gè)類, 那么就會(huì)在這個(gè)類的常量池中出現(xiàn)以下信息:
(9) CONSTANT_Fieldref_info
常量池中的一個(gè)CONSTANT_Fieldref_info, 可以看做是CONSTANT_Field數(shù)據(jù)類型的一個(gè)實(shí)例。 該數(shù)據(jù)項(xiàng)表示對(duì)一個(gè)字段的符號(hào)引用, 可以是對(duì)本類中的字段的符號(hào)引用, 也可以是對(duì)其他類中的字段的符號(hào)引用, 可以是對(duì)成員變量字段的符號(hào)引用, 也可以是對(duì)靜態(tài)變量的符號(hào)引用, 其中ref三個(gè)字母就是reference的簡(jiǎn)寫。 之前的文章中, “符號(hào)引用”這個(gè)名詞出現(xiàn)了很多次, 可能有的同學(xué)一直不是很明白, 等介紹完CONSTANT_Fieldref_info, 就可以很清晰的了解什么是符號(hào)引用。 下面分析CONSTANT_Fieldref_info中的內(nèi)容都存放了什么信息。
和其他類型的常量池?cái)?shù)據(jù)項(xiàng)一樣, 它的第一個(gè)字節(jié)也必然是tag, 它的tag值為9 。 也就是說(shuō), 當(dāng)虛擬機(jī)訪問(wèn)到一個(gè)常量池中的一項(xiàng)數(shù)據(jù), 如果發(fā)現(xiàn)這個(gè)數(shù)據(jù)的tag值為9, 就可以確定這個(gè)被訪問(wèn)的數(shù)據(jù)項(xiàng)是一個(gè)CONSTANT_Fieldref_info, 并且知道這個(gè)數(shù)據(jù)項(xiàng)表示對(duì)一個(gè)字段的符號(hào)引用。
tag值下面的兩個(gè)字節(jié)是一個(gè)叫做class_index的索引值, 它指向一個(gè)CONSTANT_Class_info數(shù)據(jù)項(xiàng), 這個(gè)數(shù)據(jù)項(xiàng)表示被引用的字段所在的類型, 包括接口。 所以說(shuō), CONSTANT_Class_info可以作為字段符號(hào)引用的一部分。
class_index以下的兩個(gè)字節(jié)是一個(gè)叫做name_and_type_index的索引, 它指向一個(gè)CONSTANT_NameAndType_info, 這個(gè)CONSTANT_NameAndType_info前面的博客中已經(jīng)解釋過(guò)了, 不明白的朋友可以先看前面的博客:深入理解JVM Class文件格式(三) 。 這個(gè)CONSTANT_NameAndType_info描述的是被引用的字段的名稱和描述符。 我們?cè)谇懊娴牟┛椭幸蔡岬竭^(guò), CONSTANT_NameAndType_info可以作為字段符號(hào)引用的一部分。
到此, 我們可以說(shuō), CONSTANT_Fieldref_info就是對(duì)一個(gè)字段的符號(hào)引用, 這個(gè)符號(hào)引用包括兩部分, 一部分是該字段所在的類, 另一部分是該字段的字段名和描述符。 這就是所謂的 “對(duì)字段的符號(hào)引用” 。
下面結(jié)合實(shí)際代碼來(lái)說(shuō)明, 代碼如下:
package com.jg.zhang;public class TestInt {int a = 10;void print(){System.out.println(a);} }在print方法中, 引用了本類中的字段a。 代碼很簡(jiǎn)單, 我們一眼就可以看到print方法中是如何引用本類中定義的字段a的。 那么在class文件中, 對(duì)字段a的引用是如何描述的呢? 下面我們將這段代碼使用javap反編譯, 給出簡(jiǎn)化后的反編譯結(jié)果:
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=10: getstatic #19 // Field java/lang/System.out:Ljava/io/PrintStream;3: aload_04: getfield #12 // Field a:I7: invokevirtual #25 // Method java/io/PrintStream.println:(I)V10: return }可以看到, print方法的位置為4的字節(jié)碼指令getfield引用了索引為12的常量池?cái)?shù)據(jù)項(xiàng), 常量池中索引為12的數(shù)據(jù)項(xiàng)是一個(gè)CONSTANT_Fieldref_info, 這個(gè)CONSTANT_Fieldref_info又引用了索引為1和13的兩個(gè)數(shù)據(jù)項(xiàng), 索引為1的數(shù)據(jù)項(xiàng)是一個(gè)CONSTANT_Class_info, 這個(gè)CONSTANT_Class_info數(shù)據(jù)項(xiàng)又引用了索引為2的數(shù)據(jù)項(xiàng), 索引為2的數(shù)據(jù)項(xiàng)是一個(gè)CONSTANT_Utf8_info , 他存儲(chǔ)了字段a所在的類的全限定名com/jg/zhang/TestInt 。 而CONSTANT_Fieldref_info所引用的索引為13的數(shù)據(jù)項(xiàng)是一個(gè)CONSTANT_NameAndType_info, 它又引用了兩個(gè)數(shù)據(jù)項(xiàng), 分別為第5項(xiàng)和第6項(xiàng), 這是兩個(gè)CONSTANT_Utf8_info , 分別存儲(chǔ)了字段a的字段名a, 和字段a的描述符I 。
下面給出內(nèi)存布局圖, 這個(gè)圖中涉及的東西有點(diǎn)多, 因?yàn)镃ONSTANT_Fieldref_info引用了CONSTANT_Class_info和CONSTANT_NameAndType_info, CONSTANT_Class_info又引用了一個(gè)CONSTANT_Utf8_info , 而CONSTANT_NameAndType_info又引用了兩個(gè)CONSTANT_Utf8_info 。
(10) CONSTANT_Methodref_info
常量池中的一個(gè)CONSTANT_Methodref_info, 可以看做是CONSTANT_Methodref數(shù)據(jù)類型的一個(gè)實(shí)例。 該數(shù)據(jù)項(xiàng)表示對(duì)一個(gè)類中方法的符號(hào)引用, 可以是對(duì)本類中的方法的符號(hào)引用, 也可以是對(duì)其他類中的方法的符號(hào)引用, 可以是對(duì)成員方法字段的符號(hào)引用, 也可以是對(duì)靜態(tài)方法的符號(hào)引用,但是不會(huì)是對(duì)接口中的方法的符號(hào)引用。 其中ref三個(gè)字母就是reference的簡(jiǎn)寫。 在上一小節(jié)中介紹了CONSTANT_Fieldref_info, 它是對(duì)字段的符號(hào)引用, 本節(jié)中介紹的CONSTANT_Methodref_info和CONSTANT_Fieldref_info很相似。既然是符號(hào)“引用”, 那么只有在原文件中調(diào)用了一個(gè)方法, 常量池中才有和這個(gè)被調(diào)用方法的相對(duì)應(yīng)的符號(hào)引用, 即存在一個(gè)CONSTANT_Methodref_info。 如果只是在類中定義了一個(gè)方法, 但是沒(méi)調(diào)用它, 則不會(huì)在常量池中出現(xiàn)和這個(gè)方法對(duì)應(yīng)的CONSTANT_Methodref_info 。
和其他類型的常量池?cái)?shù)據(jù)項(xiàng)一樣, 它的第一個(gè)字節(jié)也必然是tag, 它的tag值為10 。 也就是說(shuō), 當(dāng)虛擬機(jī)訪問(wèn)到一個(gè)常量池中的一項(xiàng)數(shù)據(jù), 如果發(fā)現(xiàn)這個(gè)數(shù)據(jù)的tag值為10, 就可以確定這個(gè)被訪問(wèn)的數(shù)據(jù)項(xiàng)是一個(gè)CONSTANT_Methodref_info, 并且知道這個(gè)數(shù)據(jù)項(xiàng)表示對(duì)一個(gè)方法的符號(hào)引用。
tag值下面的兩個(gè)字節(jié)是一個(gè)叫做class_index的索引值, 它指向一個(gè)CONSTANT_Class_info數(shù)據(jù)項(xiàng), 這個(gè)數(shù)據(jù)項(xiàng)表示被引用的方法所在的類型。 所以說(shuō), CONSTANT_Class_info可以作為方法符號(hào)引用的一部分。
class_index以下的兩個(gè)字節(jié)是一個(gè)叫做name_and_type_index的索引, 它指向一個(gè)CONSTANT_NameAndType_info, 這個(gè)CONSTANT_NameAndType_info前面的博客中已經(jīng)解釋過(guò)了, 不明白的朋友可以先看前面的博客:深入理解JVM Class文件格式(三) 。 這個(gè)CONSTANT_NameAndType_info描述的是被引用的方法的名稱和描述符。 我們?cè)谇懊娴牟┛椭幸蔡岬竭^(guò), CONSTANT_NameAndType_info可以作為方法符號(hào)引用的一部分。
到此, 我們可以知道, CONSTANT_Methodref_info就是對(duì)一個(gè)方法的符號(hào)引用, 這個(gè)符號(hào)引用包括兩部分, 一部分是該方法所在的類, 另一部分是該方法的方法名和描述符。 這就是所謂的 “對(duì)方法的符號(hào)引用” 。下面結(jié)合實(shí)際代碼來(lái)說(shuō)明, 代碼如下:
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...");} }上面的代碼包括兩個(gè)類, 其中Programer類引用了Computer類, 在Programer類的doWork方法中引用(調(diào)用)了Computer類的calculate方法。源碼中對(duì)一個(gè)方法的描述形式我們?cè)偈煜げ贿^(guò)了, 現(xiàn)在我們就反編譯Programer, 看看Programer中對(duì)Computer的doWork方法的引用, 在class文件中是如何描述的。
下面給出Programer的反編譯結(jié)果, 其中省去了一些不相關(guān)的信息:
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_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: getfield #13 // Field computer:Lcom/jg/zhang/Computer;4: invokevirtual #20 // Method com/jg/zhang/Computer.calculate:()V7: return }可以看到, doWork方法的位置為4的字節(jié)碼指令invokevirtual引用了索引為20的常量池?cái)?shù)據(jù)項(xiàng), 常量池中索引為20的數(shù)據(jù)項(xiàng)是一個(gè)CONSTANT_Methodref_info, 這個(gè)CONSTANT_Methodref_info又引用了索引為21和23的兩個(gè)數(shù)據(jù)項(xiàng), 索引為21的數(shù)據(jù)項(xiàng)是一個(gè)CONSTANT_Class_info, 這個(gè)CONSTANT_Class_info數(shù)據(jù)項(xiàng)又引用了索引為22的數(shù)據(jù)項(xiàng), 索引為22的數(shù)據(jù)項(xiàng)是一個(gè)CONSTANT_Utf8_info , 他存儲(chǔ)了被引用的Computer類中的calculate方法所在的類的全限定名com/jg/zhang/Computer 。 而CONSTANT_Methodref_info所引用的索引為23的數(shù)據(jù)項(xiàng)是一個(gè)CONSTANT_NameAndType_info, 它又引用了兩個(gè)數(shù)據(jù)項(xiàng), 分別為第24項(xiàng)和第12項(xiàng), 這是兩個(gè)CONSTANT_Utf8_info , 分別存儲(chǔ)了被引用的方法calculate的方法名calculate, 和該方法的描述符()V 。
下面給出內(nèi)存布局圖, 這個(gè)圖中涉及的東西同樣有點(diǎn)多, 因?yàn)镃ONSTANT_Methodref_info引用了CONSTANT_Class_info和CONSTANT_NameAndType_info, CONSTANT_Class_info又引用了一個(gè)CONSTANT_Utf8_info , 而CONSTANT_NameAndType_info又引用了兩個(gè)CONSTANT_Utf8_info 。
(11) CONSTANT_InterfaceMethodref_info
常量池中的一個(gè)CONSTANT_InterfaceMethodref_info, 可以看做是CONSTANT_InterfaceMethodref數(shù)據(jù)類型的一個(gè)實(shí)例。 該數(shù)據(jù)項(xiàng)表示對(duì)一個(gè)接口方法的符號(hào)引用, 不能是對(duì)類中的方法的符號(hào)引用。 其中ref三個(gè)字母就是reference的簡(jiǎn)寫。 在上一小節(jié)中介紹了CONSTANT_Methodref_info, 它是對(duì)類中的方法的符號(hào)引用, 本節(jié)中介紹的CONSTANT_InterfaceMethodref和CONSTANT_Methodref_info很相似。既然是符號(hào)“引用”, 那么只有在原文件中調(diào)用了一個(gè)接口中的方法, 常量池中才有和這個(gè)被調(diào)用方法的相對(duì)應(yīng)的符號(hào)引用, 即存在一個(gè)CONSTANT_InterfaceMethodref。 如果只是在接口中定義了一個(gè)方法, 但是沒(méi)調(diào)用它, 則不會(huì)在常量池中出現(xiàn)和這個(gè)方法對(duì)應(yīng)的CONSTANT_InterfaceMethodref 。
和其他類型的常量池?cái)?shù)據(jù)項(xiàng)一樣, 它的第一個(gè)字節(jié)也必然是tag, 它的tag值為11 。 也就是說(shuō), 當(dāng)虛擬機(jī)訪問(wèn)到一個(gè)常量池中的一項(xiàng)數(shù)據(jù), 如果發(fā)現(xiàn)這個(gè)數(shù)據(jù)的tag值為11, 就可以確定這個(gè)被訪問(wèn)的數(shù)據(jù)項(xiàng)是一個(gè)CONSTANT_InterfaceMethodref, 并且知道這個(gè)數(shù)據(jù)項(xiàng)表示對(duì)一個(gè)接口中的方法的符號(hào)引用。
tag值下面的兩個(gè)字節(jié)是一個(gè)叫做class_index的索引值, 它指向一個(gè)CONSTANT_Class_info數(shù)據(jù)項(xiàng), 這個(gè)數(shù)據(jù)項(xiàng)表示被引用的方法所在的接口。 所以說(shuō), CONSTANT_Class_info可以作為方法符號(hào)引用的一部分。
class_index以下的兩個(gè)字節(jié)是一個(gè)叫做name_and_type_index的索引, 它指向一個(gè)CONSTANT_NameAndType_info, 這個(gè)CONSTANT_NameAndType_info前面的博客中已經(jīng)解釋過(guò)了, 不明白的朋友可以先看前面的博客:深入理解JVM Class文件格式(三) 。 這個(gè)CONSTANT_NameAndType_info描述的是被引用的方法的名稱和描述符。 我們?cè)谇懊娴牟┛椭幸蔡岬竭^(guò), CONSTANT_NameAndType_info可以作為方法符號(hào)引用的一部分。
到此, 我們可以知道, CONSTANT_InterfaceMethodref就是對(duì)一個(gè)接口中的方法的符號(hào)引用, 這個(gè)符號(hào)引用包括兩部分, 一部分是該方法所在的接口, 另一部分是該方法的方法名和描述符。 這就是所謂的 “對(duì)接口中的方法的符號(hào)引用” 。
下面結(jié)合實(shí)際代碼來(lái)說(shuō)明, 代碼如下:
public class Plane {IFlyable flyable;void flyToSky(){flyable.fly();} }package com.jg.zhang;public interface IFlyable {void fly(); }在上面的代碼中, 定義一個(gè)類Plane, 在這個(gè)類中有一個(gè)IFlyable接口類型的字段flyable, 然后在Plane的flyToSky方法中調(diào)用了IFlyable中的fly方法。 這就是源代碼中對(duì)一個(gè)接口中的方法的引用方式, 下面我們反編譯Plane, 看看在class文件層面, 對(duì)一個(gè)接口中的方法的引用是如何描述的。
 下面給出反編譯結(jié)果, 為了簡(jiǎn)潔期間, 省略了一些不相關(guān)的內(nèi)容:
可以看到, flyToSky方法的位置為4的字節(jié)碼指令invokeinterface引用了索引為19的常量池?cái)?shù)據(jù)項(xiàng), 常量池中索引為19的數(shù)據(jù)項(xiàng)是一個(gè)CONSTANT_InterfaceMethodref_info, 這個(gè)CONSTANT_InterfaceMethodref_info又引用了索引為20和22的兩個(gè)數(shù)據(jù)項(xiàng), 索引為20的數(shù)據(jù)項(xiàng)是一個(gè)CONSTANT_Class_info, 這個(gè)CONSTANT_Class_info數(shù)據(jù)項(xiàng)又引用了索引為21的數(shù)據(jù)項(xiàng), 索引為21的數(shù)據(jù)項(xiàng)是一個(gè)CONSTANT_Utf8_info , 他存儲(chǔ)了被引用的方法fly所在的接口的全限定名com/jg/zhang/IFlyable 。 而CONSTANT_InterfaceMethodref_info所引用的索引為22的數(shù)據(jù)項(xiàng)是一個(gè)CONSTANT_NameAndType_info, 它又引用了兩個(gè)數(shù)據(jù)項(xiàng), 分別為第23項(xiàng)和第8項(xiàng), 這是兩個(gè)CONSTANT_Utf8_info , 分別存儲(chǔ)了被引用的方法fly的方法名fly, 和該方法的描述符()V 。
下面給出內(nèi)存布局圖, 這個(gè)圖中涉及的東西同樣有點(diǎn)多, 因?yàn)镃ONSTANT_InterfaceMethodref_info引用了CONSTANT_Class_info和CONSTANT_NameAndType_info, CONSTANT_Class_info又引用了一個(gè)CONSTANT_Utf8_info , 而CONSTANT_NameAndType_info又引用了兩個(gè)CONSTANT_Utf8_info 。
總結(jié)
到此為止, class文件中的常量池部分就已經(jīng)講解完了。 進(jìn)行一下總結(jié)。對(duì)于深入理解Java和JVM , 理解class文件的格式至關(guān)重要, 而在class文件中, 常量池是一項(xiàng)非常重要的信息。 常量池中有11種數(shù)據(jù)項(xiàng), 這個(gè)11種數(shù)據(jù)項(xiàng)存儲(chǔ)了各種信息, 包括常量字符串, 類的信息, 方法的符號(hào)引用, 字段的符號(hào)引用等等。 常量池中的數(shù)據(jù)項(xiàng)通過(guò)索引來(lái)訪問(wèn), 訪問(wèn)形式類似于數(shù)組。 常量池中的各個(gè)數(shù)據(jù)項(xiàng)之前會(huì)通過(guò)索引相互引用, class文件的其他地方也會(huì)引用常量池中的數(shù)據(jù)項(xiàng) , 如方法的字節(jié)碼指令。
在下面的文章中, 會(huì)繼續(xù)介紹class文件中, 位于常量池以下的其他信息。 這些信息包括:對(duì)本類的描述, 對(duì)父類的描述, 對(duì)實(shí)現(xiàn)的接口的描述, 本類中聲明的字段的描述, 本類匯總定義的方法的描述,還有各種屬性。
總結(jié)
以上是生活随笔為你收集整理的深入理解 JVM Class文件格式(五)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
 
                            
                        - 上一篇: 深入理解 JVM Class文件格式(四
- 下一篇: 最快的懒人减肥方法
