前情回顾
在上一章我们介绍了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文件的访问标志,类索引、父类索引和接口索引。