class常量池(other)

365 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情

class常量池是Class文件中非常重要的一个数据区域,里面存储着很多结构体,这些结构体保证了我们java程序可以正常运行。下文理解class常量池中其他结构体。

String的cp_info

String的常量池项结构为CONSTANT_String_info。所对应的结构如下:

image-20220807164705511.png

String常量在常量池中的表示,为一个CONSTANT_String_info结构体,这个结构体除了一个tag外,还有一个指向CONSTANT_Utf8_info结构体的索引string_index。

所以说每一个字符串在编译的时候,编译器都会为其生成一个不重复的CONSTANT_String_info结构体,并放置于CONSTANT_pollclass常量池中,而这个结构体内的索引string_index会指向某个CONSTANT_Utf8_info结构体,在CONSTANT_Utf8_info结构体内才正真存储着字符串的字面量信息。

CONSTANT_Utf8_info结构体的结构为:

其中legth为字节数组长度

bytes[length]存储着字符串字面量信息的字符数组

image-20220807165558000.png

写一个类只有String类型的变量,并使用javap分析

public class CpInfoStringAndUtf8 {
    String str1 = "abc";
    String str2 = "abc1";
    
    public void test() {
        String str = "abc";
        System.out.println(str == str1);
    }
}

image-20220807164504843.png

整合起来的结构就是这个样子的:

image-20220807165610084.png

类(class)的cp_info

定义的类和在类中引用到的类在常量池中如何组织和存储的?

和String类型一样涉及到两个结构体,分别是:CONSTANT_Class_infoCONSTANTT_Utf8_info。编译器会将,定义和引用到类的完全限定名称以二进制的形式封装到CONSTANT_Class_info中,然后放入到class常量池中。结构如下:

image-20220807172355220.png

类的完全限定名称和二进制形式的完全限定名称

类的完全限定名称: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结构体

image-20220807173233303.png

以CpInfoClass进一步分析:

CpInfoClass对应的CONSTANT_Class_info在常量池中的索引为#5,其内部的class名称索引指向#23,#23对应的是一个CONSTANT_Utf8_info的这么一个结构体,存储的是CpInfoClass的二进制形式的完全限定名称。

画个图表示:

image-20220807174150819.png

小结:

  • 对于一个类或者接口,jvm编译器会将其自身、父类和接口的信息都各自封装到CONSTANT_Class_info中,并存入CONSTANT_POO常量池中
  • 只有真正使用到的类jvm编译器才会为其生成对应的CONSTANT_Class_info结构体,而对于未真正使用到的类则不会生成,比如只声明一个变量StringBuffer sb2;
字段的cp_info

在定义一个类的时候以及在方法体内都会定义一些字段,这些字段在常量池中是如何存储的呢?

涉及到三个结构体,分别是:CONSTANT_Fieldref_infoCONSTANT_Class_infoCONSTANT_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的索引,用于指向这两个结构体。

image-20220807203311563.png

通过上面的分析我们可以了解到,一个CONSTANT_Field_info结构的大概样子。

CONSTANT_Field_info内部包含一个类的索引和一个NameAndType的索引,而类的索引内部包含一个类名(name_index)索引,那么这个NameAndType其内部是什么样子的?

CONSTANT_NameAndIndex_info内部包含一个 name_index索引指向程序员自定义的字段名称(比如说上面定义的sb sb2),和一个字段描述的索引descriptor_index指向该字段描述的索引(比如上面定义的Ljava/lang/StringBuilder;)

image-20220807203919295.png

image-20220807204503481.png

那么一个字段的结构信息就可以表示为:

field字段描述信息 = field字段所属的类 . field字段名称 : field字段描述

一个CONSTANT_Field_info与其他结构体的关系可以表示为:

image-20220807205719621.png

NameAndType

CONSTANT_NameAndType_info结构体中关于字段的描述:

  • 对于基本数据类型
类型描述说明
byteB表示一个字节整型
shortS短整型
intI整型
longJ长整型
floatF单精度浮点数
doubleD双精度浮点数
charC字符
booleanZ布尔类型
  • 对于引用类型来说

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_indexname_and_type_index

image-20220807215324127.png

写一个类:添加一个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分析:

image-20220807214416830.png

一个方法的结构体信息表示:

方法结构体信息 = 方法所属的类 . 方法名称:(参数说明)返回值

【(参数说明)返回值】就是方法的描述信息。

比如我有一个方法:String getMsg(); 那么描述信息就可以表示为:()Ljava/lang/String

如果返回值是Void的话,则表示为V

接口方法的cp_info

类中引用到某个接口定义的方法