您的位置 首页 java

JVM系列-7. class文件之常量池

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

常量池在class文件中的位置

JVM系列-7. class文件之常量池

常量池的位置

常量池的组成

常量池组成

常量池主要包括两部分:

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

常量池项的结构

常量池项结构

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

tag值含义

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

常量池项分类

int、 float 存储方式

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

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."<init>":()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               <init>
  #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        // "<init>":()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。

常量池存储示例

long、double存储方式

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

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."<init>":()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               <init>
  #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        // "<init>":()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存储结构的类似,多个重复数值,在常量池中只有一个

常量池存储示例

String存储方式

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

CONSTANT_String_info

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

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."<init>":()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               <init>
  #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        // "<init>":()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 个索引位置。上面的图只是看了个轮廓,让我们再深入地看一下它们的组织:

String类型常量池存储示例

类在常量池中存储方式

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

CONSTANT_Class_info

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

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."<init>":()V
   #2 = Class              #19            // java/util/Date
   #3 = Methodref          #2.#18         // java/util/Date."<init>":()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               <init>
  #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         // "<init>":()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 个常量池项,如下所示:

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."<init>":()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               <init>
  #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        // "<init>":()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  

文章来源:智云一二三科技

文章标题:JVM系列-7. class文件之常量池

文章地址:https://www.zhihuclub.com/200315.shtml

关于作者: 智云科技

热门文章

网站地图