前情回顧
在上一章我們介紹了jvm、class文件和class文件中的魔數、版本號和常量池計數值(見Class文件結構(一))。本章我們來具體介紹常量池。
class文件特殊字符串
在介紹常量池之前,我們得先介紹一下在class文件中出場率較高的一些特殊字符串。
1、簡單名稱
沒有類型和參數修飾的方法或字段名稱
2、全限定名
假設一個類的全名是com.example.demo,那麼它在class文件中的全限定名就是將"."替換爲"/"。例如Object的類全名爲java.lang.Object,那麼它的全限定名就是java/lang/Object
3、描述符
描述符得分爲3種類型的描述符
(1)基本類型的描述符
基本數據類型和void類型 | 類型對應字符 |
byte | B |
char | C |
double | D |
float | F |
int | I |
long | J |
short | S |
boolean | Z |
void | V |
只要記住除了long對應J和boolean對應Z以外,其他都是對應自己的大寫字母
(2)引用類型(類和接口)的描述符
"L"+類型低的全限定名+";"
比如Object的描述符是
Ljava/lang/Object;
com.example.demo對應的就是
Lcom/example/demo;
(3)數組類型的描述符
數組類型中的每一個維度都用一個"["表示,因此整個類型對應字符串就是
若干個"["+數組中元素類型對應的字符串
比如Object[][]的描述符是
[[Ljava/lang/Object;
(4)方法描述符
格式:
(參數類型1參數類型2...)返回值類型
方法描述符 | 方法聲明 |
()I | int getSize() |
()Ljava/lang/String; | String toString() |
([Ljava/lang/String;)V | void main(String[] args) |
([BII)I | int read(byte[] b,int off,int len) |
常量池
常量池是Class文件中的資源倉庫,主要存放兩大類常量,分別是字面量和符號引用。字面量大家可以類比爲Java語言層面的常量,而符號引用則包括下面三類常量:
- 類和接口的全限定名
- 字段的名稱和描述符
- 方法的名稱和描述符
類型 | 名稱 | 數量 |
u2 | constant_pool_count | 1 |
cp_info | constant_pool | constant_pool_count-1 |
如上表所示,常量池由一個無符號數和一張表組成。無符號數constant_pool_count代表常量池容量計數值,是常量池入口參數,由於該數是從1開始計數,因此常量池實際數量是該數-1。接下來的就是常量池中的具體數據項了。
類型 | 標誌 | 描述 |
CONSTANT_Utf8_info | 1 | UTF-8編碼的字符串 |
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_InvokeDynamic_info | 18 | 表示一個動態方法調用點 |
上表中表示了常量池中的14種不同類型的常量,這十四種常量各自都有自己的結構。
CONSTANT_Utf8_info的結構:
類型 | 名稱 | 數量 |
u1 | tag | 1 |
u2 | length | 1 |
u1 | bytes | length |
tag是標誌位,用於標誌該常量的類型,比如該常量爲CONSTANT_Utf8_info,它的tag值就爲1,CONSTANT_Class_info就爲7。
length表明該字符串的長度是多少字節,而length字節後的連續數據是一個字符串。下面給出一個實例說明該常量的結構。
Comments.java
package com.zust.bean;
public class Comments {
private String comments_no;
private String comments_name;
private String comments_context;
private String news_no;
public String getComments_name() {
return comments_name;
}
public void setComments_name(String comments_name) {
this.comments_name = comments_name;
}
public String getComments_context() {
return comments_context;
}
public void setComments_context(String comments_context) {
this.comments_context = comments_context;
}
public String getNews_no() {
return news_no;
}
public void setNews_no(String news_no) {
this.news_no = news_no;
}
public String getComments_no() {
return comments_no;
}
public void setComments_no(String comments_no) {
this.comments_no = comments_no;
}
}
用winhex打開Comments.class
第9個和第10個字節說明常量池中有38個常量,我們從第11個字節開始,這個字節是07,表明這個常量是CONSTANT_Class_info,這個類型的常量有3個字節,我們先跳過這個常量(因爲還沒介紹過它的結構QAQ),我們來到第14個字節,它是01,表明這個常量是CONSTANT_Utf8_info,接下來的2個字節是00 16說明它的長度是22,於是我們可以確定從第17個字節開始到第38個字節中儲存的是我們想要的字符串。
這裏用藍色字符標記了第17-38的字節,我們在右側可以看到這個字符串是com/zust/bean/Comments,是這個類的全限定名,可以驗證上述推論是正確的。
下面我們介紹一個符號引用的常量:
CONSTANT_Class_info結構:
類型 | 名稱 | 數量 |
u1 | tag | 1 |
u2 | name_index | 1 |
tag和CONSTANT_Utf8_info中的一樣都是標誌位。name_index是一個索引值,表示指向常量池中的第幾個常量,由於當前常量是類或接口的符號引用,所以name_index是指向一個字符串,而常量池中存放字符串的就是CONSTANT_Utf8_info,所以這裏的name_index實際上就是指向CONSTANT_Utf8_info的。
下面我們用一個專門分析Class文件字節碼的工具javap來分析上文中的Comments.class。
我們敲入javap -verbose Comments.class命令,得到以下內容,下面只截取了部分需要用到的。
Last modified 2019-7-4; size 1166 bytes
MD5 checksum f7b9e128edcf563d8216d36602fda343
Compiled from "Comments.java"
public class com.zust.bean.Comments
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #2 // com/zust/bean/Comments
#2 = Utf8 com/zust/bean/Comments
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 comments_no
#6 = Utf8 Ljava/lang/String;
#7 = Utf8 comments_name
#8 = Utf8 comments_context
#9 = Utf8 news_no
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Methodref #3.#14 // java/lang/Object."<init>":()V
#14 = NameAndType #10:#11 // "<init>":()V
#15 = Utf8 LineNumberTable
#16 = Utf8 LocalVariableTable
#17 = Utf8 this
#18 = Utf8 Lcom/zust/bean/Comments;
#19 = Utf8 getComments_name
#20 = Utf8 ()Ljava/lang/String;
#21 = Fieldref #1.#22 // com/zust/bean/Comments.comments_name:Ljava/lang/String;
#22 = NameAndType #7:#6 // comments_name:Ljava/lang/String;
#23 = Utf8 setComments_name
#24 = Utf8 (Ljava/lang/String;)V
#25 = Utf8 getComments_context
#26 = Fieldref #1.#27 // com/zust/bean/Comments.comments_context:Ljava/lang/String;
#27 = NameAndType #8:#6 // comments_context:Ljava/lang/String;
#28 = Utf8 setComments_context
#29 = Utf8 getNews_no
#30 = Fieldref #1.#31 // com/zust/bean/Comments.news_no:Ljava/lang/String;
#31 = NameAndType #9:#6 // news_no:Ljava/lang/String;
#32 = Utf8 setNews_no
#33 = Utf8 getComments_no
#34 = Fieldref #1.#35 // com/zust/bean/Comments.comments_no:Ljava/lang/String;
#35 = NameAndType #5:#6 // comments_no:Ljava/lang/String;
#36 = Utf8 setComments_no
#37 = Utf8 SourceFile
#38 = Utf8 Comments.java
前情回顧
在上一章我們介紹了jvm、class文件和class文件中的魔數、版本號和常量池計數值。本章我們來具體介紹常量池。
class文件特殊字符串
在介紹常量池之前,我們得先介紹一下在class文件中出場率較高的一些特殊字符串。
1、簡單名稱
沒有類型和參水修飾的方法或字段名稱
2、全限定名
假設一個類的全名是com.example.demo,那麼它在class文件中的全限定名就是將"."替換爲"/"。例如Object的類全名爲java.lang.Object,那麼它的全限定名就是java/lang/Object
3、描述符
描述符得分爲3種類型的描述符
(1)基本類型的描述符
基本數據類型和void類型 | 類型對應字符 |
byte | B |
char | C |
double | D |
float | F |
int | I |
long | J |
short | S |
boolean | Z |
void | V |
只要記住除了long對應J和boolean對應Z以外,其他都是對應自己的大寫字母
(2)引用類型(類和接口)的描述符
"L"+類型低的全限定名+";"
比如Object的描述符是
Ljava/lang/Object;
com.example.demo對應的就是
Lcom/example/demo;
(3)數組類型的描述符
數組類型中的每一個維度都用一個"["表示,因此整個類型對應字符串就是
若干個"["+數組中元素類型對應的字符串
比如Object[][]的描述符是
[[Ljava/lang/Object;
(4)方法描述符
格式:
(參數類型1參數類型2...)返回值類型
方法描述符 | 方法聲明 |
()I | int getSize() |
()Ljava/lang/String; | String toString() |
([Ljava/lang/String;)V | void main(String[] args) |
([BII)I | int read(byte[] b,int off,int len) |
常量池
常量池是Class文件中的資源倉庫,主要存放兩大類常量,分別是字面量和符號引用。字面量大家可以類比爲Java語言層面的常量,而符號引用則包括下面三類常量:
- 類和接口的全限定名
- 字段的名稱和描述符
- 方法的名稱和描述符
類型 | 名稱 | 數量 |
u2 | constant_pool_count | 1 |
cp_info | constant_pool | constant_pool_count-1 |
如上表所示,常量池由一個無符號數和一張表組成。無符號數constant_pool_count代表常量池容量計數值,是常量池入口參數,由於該數是從1開始計數,因此常量池實際數量是該數-1。接下來的就是常量池中的具體數據項了。
類型 | 標誌 | 描述 |
CONSTANT_Utf8_info | 1 | UTF-8編碼的字符串 |
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_InvokeDynamic_info | 18 | 表示一個動態方法調用點 |
上表中表示了常量池中的14種不同類型的常量,這十四種常量各自都有自己的結構。
CONSTANT_Utf8_info的結構:
類型 | 名稱 | 數量 |
u1 | tag | 1 |
u2 | length | 1 |
u1 | bytes | length |
tag是標誌位,用於標誌該常量的類型,比如該常量爲CONSTANT_Utf8_info,它的tag值就爲1,CONSTANT_Class_info就爲7。
length表明該字符串的長度是多少字節,而length字節後的連續數據是一個字符串。下面給出一個實例說明該常量的結構。
Comments.java
package com.zust.bean;
public class Comments {
private String comments_no;
private String comments_name;
private String comments_context;
private String news_no;
public String getComments_name() {
return comments_name;
}
public void setComments_name(String comments_name) {
this.comments_name = comments_name;
}
public String getComments_context() {
return comments_context;
}
public void setComments_context(String comments_context) {
this.comments_context = comments_context;
}
public String getNews_no() {
return news_no;
}
public void setNews_no(String news_no) {
this.news_no = news_no;
}
public String getComments_no() {
return comments_no;
}
public void setComments_no(String comments_no) {
this.comments_no = comments_no;
}
}
用winhex打開Comments.class
第9個和第10個字節說明常量池中有38個常量,我們從第11個字節開始,這個字節是07,表明這個常量是CONSTANT_Class_info,這個類型的常量有3個字節,我們先跳過這個常量(因爲還沒介紹過它的結構QAQ),我們來到第14個字節,它是01,表明這個常量是CONSTANT_Utf8_info,接下來的2個字節是00 16說明它的長度是22,於是我們可以確定從第17個字節開始到第38個字節中儲存的是我們想要的字符串。
這裏用藍色字符標記了第17-38的字節,我們在右側可以看到這個字符串是com/zust/bean/Comments,是這個類的全限定名,可以驗證上述推論是正確的。
下面我們介紹一個符號引用的常量:
CONSTANT_Class_info結構:
類型 | 名稱 | 數量 |
u1 | tag | 1 |
u2 | name_index | 1 |
tag和CONSTANT_Utf8_info中的一樣都是標誌位。name_index是一個索引值,表示指向常量池中的第幾個常量,由於當前常量是類或接口的符號引用,所以name_index是指向一個字符串,而常量池中存放字符串的就是CONSTANT_Utf8_info,所以這裏的name_index實際上就是指向CONSTANT_Utf8_info的。
下面我們用一個專門分析Class文件字節碼的工具javap來分析上文中的Comments.class。
我們敲入javap -verbose Comments.class命令,得到以下內容,下面只截取了部分需要用到的。
Last modified 2019-7-4; size 1166 bytes
MD5 checksum f7b9e128edcf563d8216d36602fda343
Compiled from "Comments.java"
public class com.zust.bean.Comments
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #2 // com/zust/bean/Comments
#2 = Utf8 com/zust/bean/Comments
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 comments_no
#6 = Utf8 Ljava/lang/String;
#7 = Utf8 comments_name
#8 = Utf8 comments_context
#9 = Utf8 news_no
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Methodref #3.#14 // java/lang/Object."<init>":()V
#14 = NameAndType #10:#11 // "<init>":()V
#15 = Utf8 LineNumberTable
#16 = Utf8 LocalVariableTable
#17 = Utf8 this
#18 = Utf8 Lcom/zust/bean/Comments;
#19 = Utf8 getComments_name
#20 = Utf8 ()Ljava/lang/String;
#21 = Fieldref #1.#22 // com/zust/bean/Comments.comments_name:Ljava/lang/String;
#22 = NameAndType #7:#6 // comments_name:Ljava/lang/String;
#23 = Utf8 setComments_name
#24 = Utf8 (Ljava/lang/String;)V
#25 = Utf8 getComments_context
#26 = Fieldref #1.#27 // com/zust/bean/Comments.comments_context:Ljava/lang/String;
#27 = NameAndType #8:#6 // comments_context:Ljava/lang/String;
#28 = Utf8 setComments_context
#29 = Utf8 getNews_no
#30 = Fieldref #1.#31 // com/zust/bean/Comments.news_no:Ljava/lang/String;
#31 = NameAndType #9:#6 // news_no:Ljava/lang/String;
#32 = Utf8 setNews_no
#33 = Utf8 getComments_no
#34 = Fieldref #1.#35 // com/zust/bean/Comments.comments_no:Ljava/lang/String;
#35 = NameAndType #5:#6 // comments_no:Ljava/lang/String;
#36 = Utf8 setComments_no
#37 = Utf8 SourceFile
#38 = Utf8 Comments.java
圖(一)
圖(二)
圖(一)和圖(二)中的#+數字代表第幾個常量,我們可以看到第一個常量是Class,是類的符號引用,字節是 07 00 02,意爲指向第二個常量,因此在圖(一)中後方會跟上一個#2,後面的註釋就是它指向的字符串的字面量。第二個常量是utf8類型,圖二中用#2標註了出來。第三個依然是Class類型,07 00 04,指向第四個常量,第四個是utf8...,依此類推即可。
常量池中的常量類型我們已經介紹了兩個,其餘的類型我們皆可通過類似的方法推算,不再詳述,下面給出常量池中的14種常量項的結構總表。
總結
本章詳述了Class文件中常用的特殊字符串、常量池中的兩類數據項CONSTANT_utf8_info和CONSTANT_Class_info。下篇博客我們將講述Class文件的訪問標誌,類索引、父類索引和接口索引。