运行时常量池 vs 常量池
- 方法区,内部包含了运行时常量池
- 字节码文件,内部包含了常量池
- 要弄清楚运行时常量池,需要弄清楚ClassFile,因为加载类的信息都在方法区
- 要弄清楚方法区的运行时常量池,需要理解ClassFile中的常量池
一个有效的字节码文件中包含了类的版本信息、字段、方法以及接口描述信息外,还包含一项重要的信息,那就是常量池表(Constant Pool Table),包括各种字面量和对类型、域和方法的符号引用
为什么需要常量池?
一个Java源文件中的类、接口、编译后产生的字节码文件。而Java中的字节码需要数据的支持,通常这种数据会很大以至于不能直接存储到字节码里面,另外一种方式,可以存储到常量池,这个字节码中包含了指向常量池的引用。在动态链接的时候会用到运行时常量池。
如下代码,很简单输出一个Hello,但是里面包含了Object、Stirng、System、PrintStream等结构。
public class SimpleClass {
public void sayHello(){
System.out.println("Hello");
}
}
我们通过javac、javap编译成class文件再反编译得到如下:
E:\WorkingSpace\IdeaProjects\demo002\src\main\java\com\booyue\tlh>javac SimpleClass.java
E:\WorkingSpace\IdeaProjects\demo002\src\main\java\com\booyue\tlh>javap -v -p SimpleClass.class
Classfile /E:/WorkingSpace/IdeaProjects/demo002/src/main/java/com/booyue/tlh/SimpleClass.class
Last modified 2021-5-17; size 415 bytes
MD5 checksum 029285f4345cfbbe2cf9568150ccd605
Compiled from "SimpleClass.java"
public class com.booyue.tlh.SimpleClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#14 // java/lang/Object."<init>":()V
#2 = Fieldref #15.#16 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #17 // Hello
#4 = Methodref #18.#19 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #20 // com/booyue/tlh/SimpleClass
#6 = Class #21 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 sayHello
#12 = Utf8 SourceFile
#13 = Utf8 SimpleClass.java
#14 = NameAndType #7:#8 // "<init>":()V
#15 = Class #22 // java/lang/System
#16 = NameAndType #23:#24 // out:Ljava/io/PrintStream;
#17 = Utf8 Hello
#18 = Class #25 // java/io/PrintStream
#19 = NameAndType #26:#27 // println:(Ljava/lang/String;)V
#20 = Utf8 com/booyue/tlh/SimpleClass
#21 = Utf8 java/lang/Object
#22 = Utf8 java/lang/System
#23 = Utf8 out
#24 = Utf8 Ljava/io/PrintStream;
#25 = Utf8 java/io/PrintStream
#26 = Utf8 println
#27 = Utf8 (Ljava/lang/String;)V
{
public com.booyue.tlh.SimpleClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public void sayHello();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 5: 0
line 6: 8
}
SourceFile: "SimpleClass.java"
这里看到sayHello方法,Code区中的#2、#3、#4就是对应常量池中的引用。如果你接着追踪#2、#3、#4对应的类型分别是#15.#16(类型为Fieldref)、#17(类型为String)、#18.#19(类型为Methodref)。你再次往下追踪的话,发现最后应用的类型都会为:Utf8类型的字面量。只有在真正运行的时候才会转换成对应的类型。
小结
常量池,可以看做是一张表,虚拟机根据这张常量池表找到要执行的类名、方法名、参数类型、字面量的类型。
运行时常量池
- 运行时常量池(Runtime Constant Pool)是方法区的一部分
- 常量池(Constant Pool)是Class文件的一部分,用于存储编译期间生成的各种字面量与符号引用,这部分内容呢在来加载后存放到方法区的运行时常量池中
- 运行时常量池,在加载类和接口到虚拟机后,就会创建对应的运行时常量池
- JVM为每个已经加载的类型(类或者接口)维护一个常量池。池中的数据项像数组项一样是通过索引访问的
- 运行时常量池中包含多种不同的常量,包含编译期已经明确的数值字面量,也包括大批运行期才能解析到的方法或者字段引用。此时不再是常量池中的符号地址了,这里换成真实的地址
- 运行时常量池,相对于Class文件中的常量池的另一重要的特征是:具备动态性
- 运行时常量池类似于传统编程语言中的符号表(symbol table),但它所包含的数据却比符号表更加丰富一些
- 当创建类或者接口的运行时常量池时,如果构造运行时常量池所需要的内存空间超过了方法区所能提供的最大值,则JVM会抛出OutOfMemoryError异常