我们都知道,JVM运行时数据区中,有块内容也叫常量池,它位于方法区中,这两个常量池有什么关系呢?其实,可以简单这么理解,class文件中的常量池是基础,是通过字节码文件进行的静态的描述,而方法区中的常量池,是程序运行起来后,类加载器将class字节码文件进行加载,其中class文件常量池部分,动态加载到了内存中,目的地就是方法区中的常量池内。下面,我们先来看看class文件中常量池的存储结构。
常量池在class文件中的位置
常量池的组成
常量池主要包括两部分:
- constant_pool_count:常量池中cp_info的个数
- cp_info:常量池中的项
常量池项的结构
JVM虚拟机规定了不同的tag值和不同类型的字面量对应关系如下:
根据cp_info中的tag 不同的值,可以将cp_info 更细化为以下结构体:
int、float存储方式
Java语言规范规定了 int类型和Float 类型的数据类型占用 4 个字节的空间。class字节码文件中的该类型的常量是按照如下方式存储的:
示例:
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。
long、double存储方式
Java语言规范规定了 long 类型和 double类型的数据类型占用8 个字节的空间,常量池中存储结构如下:
示例:
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存储结构的类似,多个重复数值,在常量池中只有一个
String存储方式
一个String需要两个cp_info结构表示:
一个CONSTANT_String_info对象,其中string_index指向了一个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个索引位置。上面的图只是看了个轮廓,让我们再深入地看一下它们的组织:
类在常量池中存储方式
一个String需要两个cp_info结构表示:
一个CONSTANT_Class_info对象,其中name_index指向了一个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个常量池项,如下所示:
对于某个类而言,其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