JVM常量池和运行时常量池
Posted wen-pan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM常量池和运行时常量池相关的知识,希望对你有一定的参考价值。
一、类的二进制字节码包含哪些信息
要理解常量池是什么,先看看类的二进制字节码包含哪些信息!!!
- 常量池
- 类的基本信息(比如:类的访问权限、类的名称、实现了哪些接口)
- 类的方法定义(包含了虚拟机指令,也就是把我们代码编译为了虚拟机指令 )
二、通过反编译字节码验证
1、测试代码
将下面的测试代码使用javac 编译为 *.class文件
public class HelloWorld {
public static void main(String[] args) {
System.out.println("hello world");
}
}
2、javap反编译*.class字节码
先将示例代码编译为 *.class
文件,然后将class文件反编译为JVM指令码。然后观察 *.class
字节码中到底包含了哪些部分。
// ===========================================类的描述信息===============================================
Classfile /xx/xx/xx/xx/HelloWorld.class
Last modified 2021-10-12; size 569 bytes
MD5 checksum 7f4f0fe4b6e6d04ddaf30401a7b04f07
Compiled from "HelloWorld.java"
public class org.memory.jvm.t5.HelloWorld
minor version: 0
major version: 49
flags: ACC_PUBLIC, ACC_SUPER
// ===========================================常量池===============================================
Constant pool:
#1 = Methodref #6.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #23 // hello world
#4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #26 // org/memory/jvm/t5/HelloWorld
#6 = Class #27 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lorg/memory/jvm/t5/HelloWorld;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 SourceFile
#19 = Utf8 HelloWorld.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = Class #28 // java/lang/System
#22 = NameAndType #29:#30 // out:Ljava/io/PrintStream;
#23 = Utf8 hello world
#24 = Class #31 // java/io/PrintStream
#25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V
#26 = Utf8 org/memory/jvm/t5/HelloWorld
#27 = Utf8 java/lang/Object
#28 = Utf8 java/lang/System
#29 = Utf8 out
#30 = Utf8 Ljava/io/PrintStream;
#31 = Utf8 java/io/PrintStream
#32 = Utf8 println
#33 = Utf8 (Ljava/lang/String;)V
// =======================================虚拟机中执行编译的方法===========================================
{
public org.memory.jvm.t5.HelloWorld();
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 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lorg/memory/jvm/t5/HelloWorld;
// main方法JVM指令码
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
// main方法访问修饰符描述
flags: ACC_PUBLIC, ACC_STATIC
// main方法中的代码执行部分
// ===============================解释器读取下面的JVM指令解释并执行===================================
Code:
stack=2, locals=1, args_size=1
// 从常量池中符号地址为 #2 的地方,先获取静态变量System.out
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
// 从常量池中符号地址为 #3 的地方加载常量 hello world
3: ldc #3 // String hello world
// 从常量池中符号地址为 #3 的地方获取要执行的方法描述,并执行方法输出hello world
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
// main方法返回
8: return
// ==================================解释器读取上面的JVM指令解释并执行================================
// 行号映射表
LineNumberTable:
line 9: 0
line 10: 8
// 局部变量表
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
}
三、什么是常量池以及常量池的作用
1、什么是常量池
从上面的反编译字节码中可以看到,Class的常量池其实就是一张记录着该类的一些常量、方法描述、类描述、变量描述信息的表。
2、常量池中有什么内容
常量池中主要存放两类数据,
一是字面量、二是符号引用
。
字面量:
- 比如String类型的字符串值或者定义为final类型的常量的值。
符号引用:
- 类或接口的全限定名(包括他的父类和所实现的接口)
- 变量或方法的名称
- 变量或方法的描述信息
- this
可参考:https://blog.csdn.net/Hellowenpan/article/details/101389330
3、常量池的作用
在解释器解释执行每条JVM指令码的时候,根据这些指令码的符号地址
去常量池中找到对应的描述。然后解释器就知道该执行哪个类的那个方法、方法的参数是什么等。
拿上面反编译的字节码指令来说明:
- 当解释器解释执行main方法的时候,读取到下面的11行JVM指令码
0: getstatic #2
getstatic指令
表示获取一个静态变量,#2
表示该静态变量的符号地址,解释器通过#2
符号地址去常量池中查找#2
对应的静态变量- 然后解释器继续向下运行,执行第13行的
3: ldc #3
指令,该指令的含义是:从常量池中加载符号地址为 #3 的常量 - 然后解释器继续向下运行,执行第15行的
5: invokevirtual #4
指令,该指令的含义是:执行方法,那么要执行哪个方法呢?执行常量池中符号地址为#4
的方法。
// main方法JVM指令码
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
// main方法访问修饰符描述
flags: ACC_PUBLIC, ACC_STATIC
// main方法中的代码执行部分
// ===============================解释器读取下面的JVM指令解释并执行===================================
Code:
stack=2, locals=1, args_size=1
// 从常量池中符号地址为 #2 的地方,先获取静态变量System.out
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
// 从常量池中符号地址为 #3 的地方加载常量 hello world
3: ldc #3 // String hello world
// 从常量池中符号地址为 #3 的地方获取要执行的方法描述,并执行方法输出hello world
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
// main方法返回
8: return
// ==================================解释器读取上面的JVM指令解释并执行================================
四、运行时常量池
1、什么是运行时常量池
上面我们分析了常量池其实就是一张对照表,常量池是 *.class
文件中的。当类的字节码被加载到内存中后,他的常量池信息就会集中放入到一块内存,这块内存就称为运行时常量池,并且把里面的符号地址
变为真实地址
。
2、符号地址变为真实地址怎么理解
①、符号地址
从上面的反编译后的JVM字节码指令可以看到有这么一条指令0: getstatic #2
,解释器解释执行JVM指令的时候,通过指令中的 #x
去常量池中获取需要的值。这里的#2
其实就是符号地址,标识这某个变量在常量池中的某个位置。
②、真实地址
在程序运行期,当*.Class
文件被加载到内存以后,常量池中的这些描述信息就会被放到内存中,其中的 #x
会被转化为内存中的地址(真实地址)。
③、简单总结
符号地址变为真实地址其实就是,在*.class文件
被加载到内存以后,将*.class文件
中常量池中的#x
符号地址,转化为内存中的地址。
以上是关于JVM常量池和运行时常量池的主要内容,如果未能解决你的问题,请参考以下文章