携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情
class常量池是Class文件中非常重要的一个数据区域,里面存储着很多结构体,这些结构体保证了我们java程序可以正常运行。下文理解class常量池中其他结构体。
String的cp_info
String的常量池项结构为
CONSTANT_String_info
。所对应的结构如下:
String常量在常量池中的表示,为一个
CONSTANT_String_info
结构体,这个结构体除了一个tag外,还有一个指向CONSTANT_Utf8_info
结构体的索引string_index。所以说每一个字符串在编译的时候,编译器都会为其生成一个不重复的
CONSTANT_String_info
结构体,并放置于CONSTANT_poll
class常量池中,而这个结构体内的索引string_index会指向某个CONSTANT_Utf8_info
结构体,在CONSTANT_Utf8_info
结构体内才正真存储着字符串的字面量信息。
CONSTANT_Utf8_info
结构体的结构为:
其中legth为字节数组长度
bytes[length]存储着字符串字面量信息的字符数组
写一个类只有String类型的变量,并使用javap分析
public class CpInfoStringAndUtf8 {
String str1 = "abc";
String str2 = "abc1";
public void test() {
String str = "abc";
System.out.println(str == str1);
}
}
复制代码
整合起来的结构就是这个样子的:
类(class)的cp_info
定义的类和在类中引用到的类在常量池中如何组织和存储的?
和String类型一样涉及到两个结构体,分别是:
CONSTANT_Class_info
和CONSTANTT_Utf8_info
。编译器会将,定义和引用到类的完全限定名称以二进制的形式封装到CONSTANT_Class_info
中,然后放入到class常量池中。结构如下:
类的完全限定名称和二进制形式的完全限定名称
类的完全限定名称:
com.roily.jvm.day03.CpInfoIntAndFloat3
,以点·分隔二进制形式的类的完全限定名称:编译器在编译时,会将点替换为/,然后存入class文件,所以称呼
com\roily\jvm\day03\CpInfoIntAndFloat3
为二进制形式的类的完全限定名称。
具体如何存储
写一个类:
public class CpInfoClass {
/**
* new关键字,真正使用到了该类。编译器会将对应的Class_info存入class常量池
*/
StringBuilder sb = new StringBuilder();
/**
* 只是单纯声明,并没有真正使用到了该类。编译器不会会将对应的Class_info存入class常量池
*/
StringBuffer sb2;
}
复制代码
javap -v分析:
存在三个
CONSTANT_Class_info
结构体
CpInfoClass
表示当前类
StringBuilder
是我们通过new
关键字直接使用的
Object
是所有类的父类,所以即便不显示继承,也会生成一个class_info
对于StringBuffer来说,当前类并没有真正使用到它,所以编译器不会为其生成对应的class_info结构体
以CpInfoClass进一步分析:
CpInfoClass对应的CONSTANT_Class_info
在常量池中的索引为#5,其内部的class名称索引指向#23,#23对应的是一个CONSTANT_Utf8_info
的这么一个结构体,存储的是CpInfoClass的二进制形式的完全限定名称。
画个图表示:
小结:
- 对于一个类或者接口,jvm编译器会将其自身、父类和接口的信息都各自封装到
CONSTANT_Class_info
中,并存入CONSTANT_POO
常量池中 - 只有真正使用到的类jvm编译器才会为其生成对应的
CONSTANT_Class_info
结构体,而对于未真正使用到的类则不会生成,比如只声明一个变量StringBuffer sb2;
字段的cp_info
在定义一个类的时候以及在方法体内都会定义一些字段,这些字段在常量池中是如何存储的呢?
涉及到三个结构体,分别是:
CONSTANT_Fieldref_info
、CONSTANT_Class_info
和CONSTANT_NameAndType_info
写一个类定义两个字段,并为其生成getter and setter方法:
public class CpInfoField {
StringBuilder sb = new StringBuilder();
StringBuffer sb2;
//getter and setter
}
复制代码
使用javap -v 分析:
jvm在编译的时候会为每一个字段生成对应的
CONSTANT_Field_info
结构体,并且在使用到该字段的地方都会指向这个结构体。
CONSTANT_Field_info
结构体内保存着,class_index和nameAndType_index的索引,用于指向这两个结构体。
通过上面的分析我们可以了解到,一个CONSTANT_Field_info
结构的大概样子。
CONSTANT_Field_info
内部包含一个类的索引和一个NameAndType的索引,而类的索引内部包含一个类名(name_index)索引,那么这个NameAndType其内部是什么样子的?
CONSTANT_NameAndIndex_info
内部包含一个 name_index索引指向程序员自定义的字段名称(比如说上面定义的sb sb2),和一个字段描述的索引descriptor_index
指向该字段描述的索引(比如上面定义的Ljava/lang/StringBuilder;
)
那么一个字段的结构信息就可以表示为:
field字段描述信息 = field字段所属的类 . field字段名称 : field字段描述
一个CONSTANT_Field_info
与其他结构体的关系可以表示为:
NameAndType
CONSTANT_NameAndType_info
结构体中关于字段的描述:
- 对于基本数据类型
类型 | 描述 | 说明 |
---|---|---|
byte | B | 表示一个字节整型 |
short | S | 短整型 |
int | I | 整型 |
long | J | 长整型 |
float | F | 单精度浮点数 |
double | D | 双精度浮点数 |
char | C | 字符 |
boolean | Z | 布尔类型 |
- 对于引用类型来说
L<ClassName>
。
比如StringBuilder类型的描述信息为:Ljava/lang/StringBuilder
- 对于数组类型来说
[<descriptor>
一个左中括号加上数组元素类型。
比如long[] ls = {1L,2L};对应描述信息为:[J
小结
- jvm编译器会为每一个有效使用的字段生成一个对应的
CONSTANT_Field_info
结构体,该结构体内包含了一个class_index
指向该字段所在类的结构体索引值,和一个name_and_type_index
指向该字段名称和描述信息的结构体索引值 - 如果一个字段没有被使用到,jvm不会将其放入常量池中
方法的cp_info
和字段的cp_info相似,jvm编译时会将每一个方法(前提是使用到)包装成一个
CONSTANT_Methodref_ingo
结构体,放入常量池,该结构体内存在两个索引值分别是Class_index
和name_and_type_index
。
写一个类:添加一个test方法对getter setter 方法进行引用:
public class CpInfoMethod {
StringBuilder sb = new StringBuilder();
StringBuffer sb2;
//getter and setter
public void test(){
getSb();
setSb(new StringBuilder("xxx"));
}
}
复制代码
javap -v分析:
一个方法的结构体信息表示:
方法结构体信息 = 方法所属的类 . 方法名称:(参数说明)返回值
【(参数说明)返回值】就是方法的描述信息。
比如我有一个方法:String getMsg(); 那么描述信息就可以表示为:()Ljava/lang/String
如果返回值是Void的话,则表示为V
接口方法的cp_info
类中引用到某个接口定义的方法