常量池中各數(shù)據(jù)項(xiàng)類型詳解
常量池中的數(shù)據(jù)項(xiàng)是通過索引來引用的, 常量池中的各個(gè)數(shù)據(jù)項(xiàng)之間也會(huì)相互引用。在這11中常量池?cái)?shù)據(jù)項(xiàng)類型中, 有兩種比較基礎(chǔ), 之所以說它們基礎(chǔ), 是因?yàn)檫@兩種類型的數(shù)據(jù)項(xiàng)會(huì)被其他類型的數(shù)據(jù)項(xiàng)引用。 這兩種數(shù)據(jù)類型就是constant_utf8 和 constant_nameandtype , 其中constant_nameandtype類型的數(shù)據(jù)項(xiàng)(constant_nameandtype_info)也會(huì)引用constant_utf8類型的數(shù)據(jù)項(xiàng)(constant_utf8_info) 。 與其他介紹常量池的書籍或其他資料不同, 本著循序漸進(jìn)和先后分明的原則, 我們首先對(duì)這兩種比較基本的類型的數(shù)據(jù)項(xiàng)進(jìn)行介紹, 然后再依次介紹其他9中數(shù)據(jù)項(xiàng)。
(1) constant_utf8_info
一個(gè)constant_utf8_info是一個(gè)constant_utf8類型的常量池?cái)?shù)據(jù)項(xiàng), 它存儲(chǔ)的是一個(gè)常量字符串。 常量池中的所有字面量幾乎都是通過constant_utf8_info描述的。下面我們首先講解constant_utf8_info數(shù)據(jù)項(xiàng)的存儲(chǔ)格式。在前面的文章中, 我們提到, 常量池中數(shù)據(jù)項(xiàng)的類型由一個(gè)整型的標(biāo)志值(tag)決定, 所以所有常量池類型的info中都必須有一個(gè)tag信息, 并且這個(gè)tag值位于數(shù)據(jù)項(xiàng)的第一個(gè)字節(jié)上。 一個(gè)11中常量池?cái)?shù)據(jù)類型, 所以就有11個(gè)tag值表示這11中類型。而constant_utf8_info的tag值為1, 也就是說如果虛擬機(jī)要解析一個(gè)常量池?cái)?shù)據(jù)項(xiàng), 首先去讀這個(gè)數(shù)據(jù)項(xiàng)的第一個(gè)字節(jié)的tag值, 如果這個(gè)tag值為1, 那么就說明這個(gè)數(shù)據(jù)項(xiàng)是一個(gè)constant_utf8類型的數(shù)據(jù)項(xiàng)。 緊挨著tag值的兩個(gè)字節(jié)是存儲(chǔ)的字符串的長(zhǎng)度length, 剩下的字節(jié)就存儲(chǔ)著字符串。 所以, 它的格式是這樣的:
其中tag占一個(gè)字節(jié), length占2個(gè)字節(jié), bytes代表存儲(chǔ)的字符串, 占length字節(jié)。所以, 如果這個(gè)constant_utf8_info存儲(chǔ)的是字符串"hello", 那么他的存儲(chǔ)形式是這樣的:
現(xiàn)在我們知道了constant_utf8_info數(shù)據(jù)項(xiàng)的存儲(chǔ)形式, 那么constant_utf8_info數(shù)據(jù)項(xiàng)都存儲(chǔ)了什么字符串呢? constant_utf8_info可包括的字符串主要以下這些:
- ?程序中的字符串常量
- ?常量池所在當(dāng)前類(包括接口和枚舉)的全限定名
- ?常量池所在當(dāng)前類的直接父類的全限定名
- ?常量池所在當(dāng)前類型所實(shí)現(xiàn)或繼承的所有接口的全限定名
- ?常量池所在當(dāng)前類型中所定義的字段的名稱和描述符
- ?常量池所在當(dāng)前類型中所定義的方法的名稱和描述符
- ?由當(dāng)前類所引用的類型的全限定名
- ?由當(dāng)前類所引用的其他類中的字段的名稱和描述符
- ?由當(dāng)前類所引用的其他類中的方法的名稱和描述符
- ?與當(dāng)前class文件中的屬性相關(guān)的字符串, 如屬性名等
總結(jié)一下, 其中有這么五類: 程序中的字符串常量, 類型的全限定名, 方法和字段的名稱, 方法和字段的描述符, 屬性相關(guān)字符串。 程序中的字符串常量不用多說了, 我們經(jīng)常使用它們創(chuàng)建字符串對(duì)象, 屬性相關(guān)的字符串, 等到講到class中的屬性信息(attibute)時(shí)自會(huì)提及。 方法和字段的名稱也不用多說了 。 剩下的就是類型的全限定名,方法和字段的描述符 。 還有一點(diǎn)需要說明, 類型的全限定名, 方法和字段的名稱, 方法和字段的描述符, 可以是本類型中定義的, 也可能是本類中引用的其他類的。
下面我們通過一個(gè)例子來進(jìn)行說明。 示例源碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package com.jg.zhang; public class programer extends person { static string company = "companya" ; static { system.out.println( "staitc init" ); } string position; computer computer; public programer() { this .position = "engineer" ; this .computer = new computer(); } public void working(){ system.out.println( "coding..." ); computer.working(); } } |
別看這個(gè)類簡(jiǎn)單, 但是反編譯后, 它的常量池有53項(xiàng)之多。 在這53項(xiàng)常量池?cái)?shù)據(jù)項(xiàng)中, 各種類型的數(shù)據(jù)項(xiàng)都有, 當(dāng)然也包括不少的constant_utf8_info 。 下面只列出反編譯后常量池中的constant_utf8_info 數(shù)據(jù)項(xiàng):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
# 2 = utf8 com/jg/zhang/programer //當(dāng)前類的全限定名 # 4 = utf8 com/jg/zhang/person //父類的全限定名 # 5 = utf8 company //company字段的名稱 # 6 = utf8 ljava/lang/string; //company和position字段的描述符 # 7 = utf8 position //position字段的名稱 # 8 = utf8 computer //computer字段的名稱 # 9 = utf8 lcom/jg/zhang/computer; //computer字段的描述符 # 10 = utf8 <clinit> //類初始化方法(即靜態(tài)初始化塊)的方法名 # 11 = utf8 ()v //working方法的描述符 # 12 = utf8 code //code屬性的屬性名 # 14 = utf8 companya //程序中的常量字符串 # 19 = utf8 java/lang/system //所引用的system類的全限定名 # 21 = utf8 out //所引用的out字段的字段名 # 22 = utf8 ljava/io/printstream; //所引用的out字段的描述符 # 24 = utf8 staitc init //程序中的常量字符串 # 27 = utf8 java/io/printstream //所引用的printstream類的全限定名 # 29 = utf8 println //所引用的println方法的方法名 # 30 = utf8 (ljava/lang/string;)v //所引用的println方法的描述符 # 31 = utf8 linenumbertable //linenumbertable屬性的屬性名 # 32 = utf8 localvariabletable //localvariabletable屬性的屬性名 # 33 = utf8 <init> //當(dāng)前類的構(gòu)造方法的方法名 # 41 = utf8 com/jg/zhang/computer //所引用的computer類的全限定名 # 45 = utf8 this //局部變量this的變量名 # 46 = utf8 lcom/jg/zhang/programer; //局部變量this的描述符 # 47 = utf8 working //woking方法的方法名 # 49 = utf8 coding... //程序中的字符串常量 # 52 = utf8 sourcefile //sourcefile屬性的屬性名 # 53 = utf8 programer.java //當(dāng)前類所在的源文件的文件名 |
上面只列出了反編譯結(jié)果中常量池中的constant_utf8_info數(shù)據(jù)項(xiàng)。 其中第三列不是javap反編譯的輸出結(jié)果, 而是我加上的注釋。 讀者可以對(duì)比上面的程序源碼來看一下, 這樣的話, 就可以清楚的看出, 源文件中的各種字符串, 是如何和存放到constant_utf8_info中的。
這里要強(qiáng)調(diào)一下, 源文件中的幾乎所有可見的字符串都存放在constant_utf8_info中, 其他類型的常量池項(xiàng)只不過是對(duì)constant_utf8_info的引用。 其他常量池項(xiàng), 把引用的constant_utf8_info組合起來, 進(jìn)而可以描述更多的信息。 下面將要介紹的constant_nameandtype_info就可以驗(yàn)證這個(gè)結(jié)論。
(2) constant_nameandtype類型的數(shù)據(jù)項(xiàng)
常量池中的一個(gè)constant_nameandtype_info數(shù)據(jù)項(xiàng), 可以看做constant_nameandtype類型的一個(gè)實(shí)例 。 從這個(gè)數(shù)據(jù)項(xiàng)的名稱可以看出, 它描述了兩種信息,第一種信息是名稱(name), 第二種信息是類型(type) 。 這里的名稱是指方法的名稱或者字段的名稱, 而type是廣義上的類型, 它其實(shí)描述的是字段的描述符或方法的描述符。 也就是說, 如果name部分是一個(gè)字段名稱, 那么type部分就是相應(yīng)字段的描述符; 如果name部分描述的是一個(gè)方法的名稱, 那么type部分就是對(duì)應(yīng)的方法的描述符。 也就是說, 一個(gè)constant_nameandtype_info就表示了一個(gè)方法或一個(gè)字段。
下面先看一下constant_nameandtype_info數(shù)據(jù)項(xiàng)的存儲(chǔ)格式。 既然是常量池中的一種數(shù)據(jù)項(xiàng)類型, 那么它的第一個(gè)字節(jié)也是tag, 它的tag值是12, 也就是說, 當(dāng)虛擬機(jī)讀到一個(gè)tag為12的常量池?cái)?shù)據(jù)項(xiàng), 就可以確定這個(gè)數(shù)據(jù)項(xiàng)是一個(gè)constant_nameandtype_info 。 tag值一下的兩個(gè)字節(jié)叫做name_index, 它指向常量池中的一個(gè)constant_utf8_info, 這個(gè)constant_utf8_info中存儲(chǔ)的就是方法或字段的名稱。 name_index以后的兩個(gè)字節(jié)叫做descriptor_index, 它指向常量池中的一個(gè)constant_utf8_info, 這個(gè)constant_utf8_info中存儲(chǔ)的就是方法或字段的描述符。 下圖表示它的存儲(chǔ)布局:
下面舉一個(gè)實(shí)例進(jìn)行說明, 實(shí)例的源碼為:
1
2
3
4
5
6
7
8
9
10
|
package com.jg.zhang; public class person { int age; int getage(){ return age; } } |
這個(gè)person類很簡(jiǎn)單, 只有一個(gè)字段age, 和一個(gè)方法getage 。 將這段代碼使用javap工具反編譯之后, 常量池信息如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
# 1 = class # 2 // com/jg/zhang/person # 2 = utf8 com/jg/zhang/person # 3 = class # 4 // java/lang/object # 4 = utf8 java/lang/object # 5 = utf8 age # 6 = utf8 i # 7 = utf8 <init> # 8 = utf8 ()v # 9 = utf8 code # 10 = methodref # 3 .# 11 // java/lang/object."<init>":()v # 11 = nameandtype # 7 :# 8 // "<init>":()v # 12 = utf8 linenumbertable # 13 = utf8 localvariabletable # 14 = utf8 this # 15 = utf8 lcom/jg/zhang/person; # 16 = utf8 getage # 17 = utf8 ()i # 18 = fieldref # 1 .# 19 // com/jg/zhang/person.age:i # 19 = nameandtype # 5 :# 6 // age:i # 20 = utf8 sourcefile # 21 = utf8 person.java |
常量池一共有21項(xiàng), 我們可以看到, 一共有兩個(gè)constant_nameandtype_info 數(shù)據(jù)項(xiàng), 分別是第#11項(xiàng)和第#19項(xiàng), 其中第#11項(xiàng)的constant_nameandtype_info又引用了常量池中的第#7項(xiàng)和第#8項(xiàng), 被引用的這兩項(xiàng)都是constant_utf8_info , 它們中存儲(chǔ)的字符串常量值分別是 <init> 和 ()v。 其實(shí)他們加起來表示的就是父類object的構(gòu)造方法。 那么這里為什么會(huì)是父類object的構(gòu)造方法而不是本類的構(gòu)造方法呢? 這是因?yàn)轭愔卸x的方法如果不被引用(也就是說在當(dāng)前類中不被調(diào)用), 那么常量池中是不會(huì)有相應(yīng)的 constant_nameandtype_info 與之對(duì)應(yīng)的, 只有引用了一個(gè)方法, 才有相應(yīng)的constant_nameandtype_info 與之對(duì)應(yīng)。 這也是為什么說constant_nameandtype_info 是方法的符號(hào)引用的一部分的原因。 (這里提到一個(gè)新的概念, 叫做方法的符號(hào)引用, 這個(gè)概念會(huì)在后面的博客中進(jìn)行講解) 可以看到, 在源碼存在兩個(gè)方法, 分別是編譯器默認(rèn)添加的構(gòu)造方法和我們自己定義的getage方法, 因?yàn)椴]有在源碼中顯示的調(diào)用這兩個(gè)方法,所以在常量池中并不存在和這兩個(gè)方法相對(duì)應(yīng)的constant_nameandtype_info 。 之所以會(huì)存在父類object的構(gòu)造方法對(duì)應(yīng)的constant_nameandtype_info , 是因?yàn)樽宇悩?gòu)造方法中會(huì)默認(rèn)調(diào)用父類的無參數(shù)構(gòu)造方法。 我們將常量中的其他信息去掉, 可以看得更直觀:
下面講解常量池第#19項(xiàng)的constant_nameandtype_info , 它引用了常量池第#5項(xiàng)和第#6項(xiàng), 這兩項(xiàng)也是constant_utf8_info 項(xiàng), 其中存儲(chǔ)的字符串分別是age和i, 其中age是源碼中字段age的字段名, i是age字段的描述符。 所以這個(gè)constant_nameandtype_info 就表示對(duì)本類中的字段age的引用。 除去常量池中的其他信息, 可以看得更直觀:
和方法相同, 只定義一個(gè)字段而不引用它(在源碼中表現(xiàn)為不訪問這個(gè)變量), 那么在常量池中也不會(huì)存在和該字段相對(duì)應(yīng)的constant_nameandtype_info 項(xiàng)。這也是為什么說constant_nameandtype_info作為字段符號(hào)引用的一部分的原因。 (這里提到一個(gè)新的概念, 叫做字段的符號(hào)引用, 這個(gè)概念會(huì)在后面的博客中進(jìn)行講解) 在本例中之所以會(huì)出現(xiàn)這個(gè)constant_nameandtype_info , 是因?yàn)樵谠创a的getage方法中訪問了這個(gè)字段:
1
2
3
|
int getage(){ return age; } |
下面給出這兩個(gè)constant_nameandtype_info真實(shí)的內(nèi)存布局圖:
和object構(gòu)造方法相關(guān)的constant_nameandtype_info的示意圖:
和age字段相關(guān)的constant_nameandtype_info示意圖:
這兩張圖能夠很好的反映出constant_nameandtype_info和constant_utf8_info 這兩種常量池?cái)?shù)據(jù)項(xiàng)的數(shù)據(jù)存儲(chǔ)方式, 也能夠真實(shí)的反應(yīng)constant_nameandtype_info和constant_utf8_info 的引用關(guān)系。
總結(jié)
在本文中我們主要介紹了常量池中的兩種數(shù)據(jù)項(xiàng): constant_nameandtype_info 和 constant_utf8_info 。 其中constant_utf8_info存儲(chǔ)的是源文件中的各種字符串, 而constant_nameandtype_info表述的是源文件中對(duì)一個(gè)字段或方法的符號(hào)引用的一部分(即 方法名加方法描述符, 或者是 字段名加字段描述符)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。