运行时常量池在哪里_JVM系列-7. class文件之常量池

我们都知道,JVM运行时数据区中,有块内容也叫常量池,它位于方法区中,这两个常量池有什么关系呢?其实,可以简单这么理解,class文件中的常量池是基础,是通过字节码文件进行的静态的描述,而方法区中的常量池,是程序运行起来后,类加载器将class字节码文件进行加载,其中class文件常量池部分,动态加载到了内存中,目的地就是方法区中的常量池内。下面,我们先来看看class文件中常量池的存储结构。

常量池在class文件中的位置

50154bcde0cb6d1c05621f6d797e01ce.png

常量池的位置

常量池的组成

d27c2666f86de88a27e2ac7d4266f301.png

常量池组成

常量池主要包括两部分:

  1. constant_pool_count:常量池中cp_info的个数
  2. cp_info:常量池中的项

常量池项的结构

e10c5172da363873243d10e4a1a3d24f.png

常量池项结构

JVM虚拟机规定了不同的tag值和不同类型的字面量对应关系如下:

e4846543bf2b5f8c29c685c769907c70.png

tag值含义

根据cp_info中的tag 不同的值,可以将cp_info 更细化为以下结构体:

721b4c1c966f3653d3c867cb3493d036.png

常量池项分类

int、float存储方式

Java语言规范规定了 int类型和Float 类型的数据类型占用 4 个字节的空间。class字节码文件中的该类型的常量是按照如下方式存储的:

ea65a3dc26be7e7c22e4572766c80137.png

int、float存储方式

示例:

package jvm.constant.pool;public class IntAndFloatTest {    private final int a = 10;    private final int b = 10;    private final float c = 11f;    private final float d = 11f;    private final float e = 11f;}

使用javap -v,截取常量池部分:

Constant pool:   #1 = Methodref          #9.#28         // java/lang/Object."":()V   #2 = Fieldref           #8.#29         // jvm/constant/pool/IntAndFloatTest.a:I   #3 = Fieldref           #8.#30         // jvm/constant/pool/IntAndFloatTest.b:I   #4 = Float              11.0f           //常量11f   #5 = Fieldref           #8.#31         // jvm/constant/pool/IntAndFloatTest.c:F   #6 = Fieldref           #8.#32         // jvm/constant/pool/IntAndFloatTest.d:F   #7 = Fieldref           #8.#33         // jvm/constant/pool/IntAndFloatTest.e:F   #8 = Class              #34            // jvm/constant/pool/IntAndFloatTest   #9 = Class              #35            // java/lang/Object  #10 = Utf8               a  #11 = Utf8               I  #12 = Utf8               ConstantValue  #13 = Integer            10             //常量10  #14 = Utf8               b  #15 = Utf8               c  #16 = Utf8               F  #17 = Utf8               d  #18 = Utf8               e  #19 = Utf8                 #20 = Utf8               ()V  #21 = Utf8               Code  #22 = Utf8               LineNumberTable  #23 = Utf8               LocalVariableTable  #24 = Utf8               this  #25 = Utf8               Ljvm/constant/pool/IntAndFloatTest;  #26 = Utf8               SourceFile  #27 = Utf8               IntAndFloatTest.java  #28 = NameAndType        #19:#20        // "":()V  #29 = NameAndType        #10:#11        // a:I  #30 = NameAndType        #14:#11        // b:I  #31 = NameAndType        #15:#16        // c:F  #32 = NameAndType        #17:#16        // d:F  #33 = NameAndType        #18:#16        // e:F  #34 = Utf8               jvm/constant/pool/IntAndFloatTest  #35 = Utf8               java/lang/Object

可以看到代码中出现了两次10三次11f,但是常量池中就只有一个10一个11f。从结果上可以看到常量池第#13 个常量池项(cp_info) 就是CONSTANT_Integer_info,值为10;第#4个常量池项(cp_info) 就是CONSTANT_Float_info,值为11f。

2b5634e8ae094546d309cb85b56a7e36.png

常量池存储示例

long、double存储方式

Java语言规范规定了 long 类型和 double类型的数据类型占用8 个字节的空间,常量池中存储结构如下:

61bfd5ec6ad45533147788f64142e67d.png

long、double存储方式

示例:

public class LongAndDoubleTest { private long a = -6076574518398440533L; private long b = -6076574518398440533L; private long c = -6076574518398440533L; private double d = 10.1234567890D; private double e = 10.1234567890D; private double f = 10.1234567890D; }
Constant pool:   #1 = Methodref          #13.#31        // java/lang/Object."":()V   #2 = Long               -6076574518398440533l  //一个   #4 = Fieldref           #12.#32        // jvm/constant/pool/LongAndDoubleTest.a:J   #5 = Fieldref           #12.#33        // jvm/constant/pool/LongAndDoubleTest.b:J   #6 = Fieldref           #12.#34        // jvm/constant/pool/LongAndDoubleTest.c:J   #7 = Double             10.123456789d   //一个   #9 = Fieldref           #12.#35        // jvm/constant/pool/LongAndDoubleTest.d:D  #10 = Fieldref           #12.#36        // jvm/constant/pool/LongAndDoubleTest.e:D  #11 = Fieldref           #12.#37        // jvm/constant/pool/LongAndDoubleTest.f:D  #12 = Class              #38            // jvm/constant/pool/LongAndDoubleTest  #13 = Class              #39            // java/lang/Object  #14 = Utf8               a  #15 = Utf8               J  #16 = Utf8               b  #17 = Utf8               c  #18 = Utf8               d  #19 = Utf8               D  #20 = Utf8               e  #21 = Utf8               f  #22 = Utf8                 #23 = Utf8               ()V  #24 = Utf8               Code  #25 = Utf8               LineNumberTable  #26 = Utf8               LocalVariableTable  #27 = Utf8               this  #28 = Utf8               Ljvm/constant/pool/LongAndDoubleTest;  #29 = Utf8               SourceFile  #30 = Utf8               LongAndDoubleTest.java  #31 = NameAndType        #22:#23        // "":()V  #32 = NameAndType        #14:#15        // a:J  #33 = NameAndType        #16:#15        // b:J  #34 = NameAndType        #17:#15        // c:J  #35 = NameAndType        #18:#19        // d:D  #36 = NameAndType        #20:#19        // e:D  #37 = NameAndType        #21:#19        // f:D  #38 = Utf8               jvm/constant/pool/LongAndDoubleTest  #39 = Utf8               java/lang/Object

结论和int和float存储结构的类似,多个重复数值,在常量池中只有一个

b0a72b6328c2ed61c8b49fdd069208da.png

常量池存储示例

String存储方式

一个String需要两个cp_info结构表示:

b1f2484db5d02b447afd1100da32ae93.png

CONSTANT_String_info

一个CONSTANT_String_info对象,其中string_index指向了一个CONSTANT_Utf8_info对象

ed635214bbd5e346f0e4dfdd815c4129.png

CONSTANT_Utf8_info

CONSTANT_Utf8_info对象中,存储了字节数组长度及utf8编码后的字节数组内容

示例:

public class StringTest {    private String s1 = "JVM原理";    private String s2 = "JVM原理";    private String s3 = "JVM原理";    private String s4 = "JVM原理";}
Constant pool:   #1 = Methodref          #8.#23         // java/lang/Object."":()V   #2 = String             #24            // JVM原理 CONSTANT_String_info指向24   #3 = Fieldref           #7.#25         // jvm/constant/pool/StringTest.s1:Ljava/lang/String;   #4 = Fieldref           #7.#26         // jvm/constant/pool/StringTest.s2:Ljava/lang/String;   #5 = Fieldref           #7.#27         // jvm/constant/pool/StringTest.s3:Ljava/lang/String;   #6 = Fieldref           #7.#28         // jvm/constant/pool/StringTest.s4:Ljava/lang/String;   #7 = Class              #29            // jvm/constant/pool/StringTest   #8 = Class              #30            // java/lang/Object   #9 = Utf8               s1  #10 = Utf8               Ljava/lang/String;  #11 = Utf8               s2  #12 = Utf8               s3  #13 = Utf8               s4  #14 = Utf8                 #15 = Utf8               ()V  #16 = Utf8               Code  #17 = Utf8               LineNumberTable  #18 = Utf8               LocalVariableTable  #19 = Utf8               this  #20 = Utf8               Ljvm/constant/pool/StringTest;  #21 = Utf8               SourceFile  #22 = Utf8               StringTest.java  #23 = NameAndType        #14:#15        // "":()V  #24 = Utf8               JVM原理   //CONSTANT_Utf8_info结构  #25 = NameAndType        #9:#10         // s1:Ljava/lang/String;  #26 = NameAndType        #11:#10        // s2:Ljava/lang/String;  #27 = NameAndType        #12:#10        // s3:Ljava/lang/String;  #28 = NameAndType        #13:#10        // s4:Ljava/lang/String;  #29 = Utf8               jvm/constant/pool/StringTest  #30 = Utf8               java/lang/Object

在面的图中,我们可以看到CONSTANT_String_info结构体位于常量池的第#2个索引位置。而存放"JVM原理" 字符串的 UTF-8编码格式的字节数组被放到CONSTANT_Utf8_info结构体中,该结构体位于常量池的第#24个索引位置。上面的图只是看了个轮廓,让我们再深入地看一下它们的组织:

f6eb505faaf65d86342549845c1673f3.png

String类型常量池存储示例

类在常量池中存储方式

一个String需要两个cp_info结构表示:

dddcd18bb18cc83c16bd68dea23c5276.png

CONSTANT_Class_info

一个CONSTANT_Class_info对象,其中name_index指向了一个CONSTANT_Utf8_info对象

ed635214bbd5e346f0e4dfdd815c4129.png

CONSTANT_Utf8_info

CONSTANT_Utf8_info对象中,存储了字节数组长度及utf8编码后的类完全限定名的内容

示例:

import java.util.Date;public class ClassTest {    private Date date =new Date();}
Constant pool:   #1 = Methodref          #6.#18         // java/lang/Object."":()V   #2 = Class              #19            // java/util/Date   #3 = Methodref          #2.#18         // java/util/Date."":()V   #4 = Fieldref           #5.#20         // com/jvm/ClassTest.date:Ljava/util/Date;   #5 = Class              #21            // com/jvm/ClassTest   #6 = Class              #22            // java/lang/Object   #7 = Utf8               date   #8 = Utf8               Ljava/util/Date;   #9 = Utf8                 #10 = Utf8               ()V  #11 = Utf8               Code  #12 = Utf8               LineNumberTable  #13 = Utf8               LocalVariableTable  #14 = Utf8               this  #15 = Utf8               Ljvm/constant/pool/ClassTest;  #16 = Utf8               SourceFile  #17 = Utf8               ClassTest.java  #18 = NameAndType        #9:#10         // "":()V  #19 = Utf8               java/util/Date  #20 = NameAndType        #7:#8          // date:Ljava/util/Date;  #21 = Utf8               com/jvm/ClassTest  #22 = Utf8               java/lang/Object

如上图所示,在ClassTest.class文件的常量池中,共有 3 个CONSTANT_Class_info结构体,分别表示ClassTest 中用到的Class信息。 看其中一个表示com/jvm/ClassTest的CONSTANT_Class_info 结构体。它在常量池中的位置是#5,它的name_index值为#21,它指向了常量池的第21个常量池项,如下所示:

dffc37f37bc432d3c255942081845558.png

class存储

对于某个类而言,其class文件中至少要有两个CONSTANT_Class_info常量池项,用来表示自己的类信息和其父类信息。(除了java.lang.Object类除外,其他的任何类都会默认继承自java.lang.Object)如果类声明实现了某些接口,那么接口的信息也会生成对应的CONSTANT_Class_info常量池项。

除此之外,如果在类中使用到了其他的类,只有真正使用到了相应的类,JDK编译器才会将类的信息组成CONSTANT_Class_info常量池项放置到常量池中。

import java.util.Date; public class Other{ private Date date; public Other() { Date da; } }

上述的Other的类,在JDK将其编译成class文件时,常量池中并没有java.util.Date对应的CONSTANT_Class_info常量池项,为什么呢?

在Other类中虽然定义了Date类型的两个变量date、da,但是JDK编译的时候,认为你只是声明了“Ljava/util/Date”类型的变量,并没有实际使用到Ljava/util/Date类。将类信息放置到常量池中的目的,是为了在后续的代码中有可能会反复用到它。很显然,JDK在编译Other类的时候,会解析到Date类有没有用到,发现该类在代码中就没有用到过,所以就认为没有必要将它的信息放置到常量池中了。将上述的Other类改写一下,仅使用new Date(),如下所示:

import java.util.Date;public class Other {    public Other() {        new Date();    }}

这时候使用javap -v Other.class,可以查看到常量池中有表示java/util/Date的常量池项。

总结一下:

1. 对于某个类或接口而言,其自身、父类和继承或实现的接口的信息会被直接组装成CONSTANT_Class_info常量池项放置到常量池中;

2. 类中或接口中使用到了其他的类,只有在类中实际使用到了该类时,该类的信息才会在常量池中有对应的CONSTANT_Class_info常量池项;

3. 类中或接口中仅仅定义某种类型的变量,JDK只会将变量的类型描述信息以UTF-8字符串组成CONSTANT_Utf8_info常量池项放置到常量池中,上面在类中的private Date date;JDK编译器只会将表示date的数据类型的“Ljava/util/Date”字符串放置到常量池中。

哪些字面量会进入常量池中

结论如下:

1. final类型的8种基本类型的值会进入常量池。

2. 非final类型(包括static的)的8种基本类型的值,只有double、float、long的值会进入常量池。

3. 常量池中包含的字符串类型字面量(双引号引起来的字符串值)。

测试代码:

public class ConstantPoolTest {    private int int_num = 110; //无    private char char_num = 'a'; //无    private short short_num = 120; //无    private float float_num = 130.0f; //有 #5    private double double_num = 140.0; //有 #7    private byte byte_num = 111; //无    private long long_num = 3333L; //有 #11    private long long_delay_num;     private boolean boolean_flage = true; //无    public void init() {        this.long_delay_num = 5555L; //有 #15    }}

测试结果:

Constant pool:   #1 = Methodref          #19.#47        // java/lang/Object."":()V   #2 = Fieldref           #18.#48        // jvm/constant/pool/ConstantPoolTest.int_num:I   #3 = Fieldref           #18.#49        // jvm/constant/pool/ConstantPoolTest.char_num:C   #4 = Fieldref           #18.#50        // jvm/constant/pool/ConstantPoolTest.short_num:S   #5 = Float              130.0f   #6 = Fieldref           #18.#51        // jvm/constant/pool/ConstantPoolTest.float_num:F   #7 = Double             140.0d   #9 = Fieldref           #18.#52        // jvm/constant/pool/ConstantPoolTest.double_num:D  #10 = Fieldref           #18.#53        // jvm/constant/pool/ConstantPoolTest.byte_num:B  #11 = Long               3333l  #13 = Fieldref           #18.#54        // jvm/constant/pool/ConstantPoolTest.long_num:J  #14 = Fieldref           #18.#55        // jvm/constant/pool/ConstantPoolTest.boolean_flage:Z  #15 = Long               5555l  #17 = Fieldref           #18.#56        // jvm/constant/pool/ConstantPoolTest.long_delay_num:J  #18 = Class              #57            // jvm/constant/pool/ConstantPoolTest  #19 = Class              #58            // java/lang/Object  #20 = Utf8               int_num  #21 = Utf8               I  #22 = Utf8               char_num  #23 = Utf8               C  #24 = Utf8               short_num  #25 = Utf8               S  #26 = Utf8               float_num  #27 = Utf8               F  #28 = Utf8               double_num  #29 = Utf8               D  #30 = Utf8               byte_num  #31 = Utf8               B  #32 = Utf8               long_num  #33 = Utf8               J  #34 = Utf8               long_delay_num  #35 = Utf8               boolean_flage  #36 = Utf8               Z  #37 = Utf8                 #38 = Utf8               ()V  #39 = Utf8               Code  #40 = Utf8               LineNumberTable  #41 = Utf8               LocalVariableTable  #42 = Utf8               this  #43 = Utf8               Ljvm/constant/pool/ConstantPoolTest;  #44 = Utf8               init  #45 = Utf8               SourceFile  #46 = Utf8               ConstantPoolTest.java  #47 = NameAndType        #37:#38        // "":()V  #48 = NameAndType        #20:#21        // int_num:I  #49 = NameAndType        #22:#23        // char_num:C  #50 = NameAndType        #24:#25        // short_num:S  #51 = NameAndType        #26:#27        // float_num:F  #52 = NameAndType        #28:#29        // double_num:D  #53 = NameAndType        #30:#31        // byte_num:B  #54 = NameAndType        #32:#33        // long_num:J  #55 = NameAndType        #35:#36        // boolean_flage:Z  #56 = NameAndType        #34:#33        // long_delay_num:J  #57 = Utf8               jvm/constant/pool/ConstantPoolTest  #58 = Utf8               java/lang/Object
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值