JVM中有:
Class文件常量池、运行时常量池、全局字符串常量池、基本类型包装类对象 常量池
Class文件常量池:
package basics; public class ConstanPoolTest { private int value = 1; public String s = "abc"; public final static int f = 0x101; public void setValue(int v) { final int temp = 3; this.value = temp + v; } public int getValue() { return value; } }
"D:\Program Files\Java\jdk1.8.0_202\bin\javap.exe" -v basics.ConstanPoolTest Classfile /D:/QFWorkspace/IdeaProjects/JavaReview/target/classes/basics/ConstanPoolTest.class Last modified 2019-8-4; size 627 bytes MD5 checksum bb29ca138910d931e8f2c777f6abb41b Compiled from "ConstanPoolTest.java" public class basics.ConstanPoolTest minor version: 0 major version: 49 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #6.#29 // java/lang/Object."<init>":()V #2 = Fieldref #5.#30 // basics/ConstanPoolTest.value:I #3 = String #31 // abc #4 = Fieldref #5.#32 // basics/ConstanPoolTest.s:Ljava/lang/String; #5 = Class #33 // basics/ConstanPoolTest #6 = Class #34 // java/lang/Object #7 = Utf8 value #8 = Utf8 I #9 = Utf8 s #10 = Utf8 Ljava/lang/String; #11 = Utf8 f #12 = Utf8 ConstantValue #13 = Integer 257 #14 = Utf8 <init> #15 = Utf8 ()V #16 = Utf8 Code #17 = Utf8 LineNumberTable #18 = Utf8 LocalVariableTable #19 = Utf8 this #20 = Utf8 Lbasics/ConstanPoolTest; #21 = Utf8 setValue #22 = Utf8 (I)V #23 = Utf8 v #24 = Utf8 temp #25 = Utf8 getValue #26 = Utf8 ()I #27 = Utf8 SourceFile #28 = Utf8 ConstanPoolTest.java #29 = NameAndType #14:#15 // "<init>":()V #30 = NameAndType #7:#8 // value:I #31 = Utf8 abc #32 = NameAndType #9:#10 // s:Ljava/lang/String; #33 = Utf8 basics/ConstanPoolTest #34 = Utf8 java/lang/Object { public java.lang.String s; descriptor: Ljava/lang/String; flags: ACC_PUBLIC public static final int f; descriptor: I flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL ConstantValue: int 257 public basics.ConstanPoolTest(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: iconst_1 6: putfield #2 // Field value:I 9: aload_0 10: ldc #3 // String abc 12: putfield #4 // Field s:Ljava/lang/String; 15: return LineNumberTable: line 3: 0 line 4: 4 line 5: 9 LocalVariableTable: Start Length Slot Name Signature 0 16 0 this Lbasics/ConstanPoolTest; public void setValue(int); descriptor: (I)V flags: ACC_PUBLIC Code: stack=3, locals=3, args_size=2 0: iconst_3 1: istore_2 2: aload_0 3: iconst_3 4: iload_1 5: iadd 6: putfield #2 // Field value:I 9: return LineNumberTable: line 9: 0 line 10: 2 line 11: 9 LocalVariableTable: Start Length Slot Name Signature 0 10 0 this Lbasics/ConstanPoolTest; 0 10 1 v I 2 8 2 temp I public int getValue(); descriptor: ()I flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #2 // Field value:I 4: ireturn LineNumberTable: line 14: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lbasics/ConstanPoolTest; } SourceFile: "ConstanPoolTest.java" Process finished with exit code 0
可以看到该class文件的版本号、常量池、已经编译后的字节码。既然是常量池,那么其中存放的肯定是常量,那么什么是“常量”呢? class文件常量池主要存放两大常量:字面量和符号引用
字面量: 字面量接近java语言层面的常量概念,主要包括:
- 文本字符串,也就是我们经常申明的: public String s = "abc";中的"abc"
- 用final修饰的成员变量,包括静态变量、实例变量和局部变量
注:上面说的存在于常量池的字面量,指的是数据的值,也就是abc和0x101(257),通过上面对常量池的观察可知这两个字面量是确实存在于常量池的。
而对于基本类型数据(甚至是方法中的局部变量),也就是上面的private int value = 1;常量池中只保留了他的的字段描述符I和字段的名称value,他们的字面量不会存在于常量池
2). 符号引用
符号引用主要设涉及编译原理方面的概念,包括下面三类常量:
- 类和接口的全限定名,也就是Ljava/lang/String;这样,将类名中原来的"."替换为"/"得到的,主要用于在运行时解析得到类的直接引用,像上面
#5 = Class #33 // JavaBasicKnowledge/JavaBean #33 = Utf8 JavaBasicKnowledge/JavaBean
- 字段的名称和描述符,字段也就是类或者接口中声明的变量,包括类级别变量和实例级的变量
#4 = Fieldref #5.#32 // JavaBasicKnowledge/JavaBean.value:I #5 = Class #33 // JavaBasicKnowledge/JavaBean #32 = NameAndType #7:#8 // value:I #7 = Utf8 value #8 = Utf8 I
//这两个是局部变量,值保留字段名称
#23 = Utf8 v
#24 = Utf8 temp
可以看到,对于方法中的局部变量名,class文件的常量池仅仅保存字段名。
- 方法中的名称和描述符,也即参数类型+返回值
#21 = Utf8 setValue #22 = Utf8 (I)V #25 = Utf8 getValue #26 = Utf8 ()I
运行时常量池
运行时常量池是方法区的一部分,所以也是全局贡献的,我们知道,jvm在执行某个类的时候,必须经过加载、链接(验证、准备、解析)、初始化,在第一步加载的时候需要完成:
- 通过一个类的全限定名来获取此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个类对象,代表加载的这个类,这个对象是java.lang.Class,它作为方法区这个类的各种数据访问的入口。
类对象和普通对象是不同的,类对象是在类加载的时候完成的,是jvm创建的并且是单例的,作为这个类和外界交互的入口, 而普通的对象一般是在调用new之后创建。
上面的第二条,将class字节流代表的静态存储结构转化为方法区的运行时数据结构,其中就包含了class文件常量池进入运行时常量池的过程,这里需要强调一下不同的类共用一个运行时常量池,同时在进入运行时常量池的过程中,多个class文件中常量池中相同的字符串只会存在一份在运行时常量池,这也是一种优化。
运行时常量池的作用是存储java class文件常量池中的符号信息,运行时常量池中保存着一些class文件中描述的符号引用,同时在类的解析阶段还会将这些符号引用翻译出直接引用(直接指向实例对象的指针,内存地址),翻译出来的直接引用也是存储在运行时常量池中。
运行时常量池相对于class常量池一大特征就是具有动态性,java规范并不要求常量只能在编译时才产生,也就是说运行时常量池的内容并不全部来自class常量池,在运行时可以通过代码生成常量并将其放入运行时常量池中,这种特性被用的最多的就是String.intern()