我们就来学习运行时常量池。
在之前呢我们介绍方法区的组成时,我们提到过运行时常量池,这个图少写两个字"运行"。
那么不管是1.6还是1.8,它们在方法区域的组成中都有一个叫运行时常量池的部分。那这个运行时常量池内部还有可能包含一个叫stringTable的东西。
那它到底是什么呢?我们要讲解运行时常量池,就要聊聊什么叫常量池。
这里我通过一个例子来给大家解释常量池的含义。先来看一下这段代码。
这个代码相信大家都非常的熟悉啊,就是一个很简单的hello world 这样一个程序啊,
那是hello world要运行,你肯定先把它编译成二进制的字节码,编译成二进制字节码。
这个字节码它有哪些部分组成呢?一般来说字节码由这三部分组成【大的方向】
第一部分是这个类的基本信息。
第二部分是类的一个常量池。
第三部分是类中的一些方法定义,这些的方法定义中就包含了虚拟机指令。
那我们下面呢就针对这三部分先来看一下。
那怎么看呢?二进制字节码都是二进制的你也看不懂。
那我们可以拿到它这个class。借助jdk提供的一个工具,把这个class 再做一次反编译。反编译后的结果,我们人类就可以勉强读懂了。
好,那下面我们先进入到这个class 的所在目录,
接着我们使用jdk中的javap来反编译刚才的这个Client.class这个字节码。-v参数是显示这个详细信息.(javap -v Client.class)
Classfile /C:/Users/89296/IdeaProjects/studyjava/target/classes/design/wlb/studyjava/demo/restudy/
c4jvm专题/chapter02/c25方法区_方法区_常量池/Client.class
Last modified 2022-6-25; size 729 bytes
MD5 checksum 957a44a38b4099f657d2703f4cd0fce1
Compiled from "Client.java"
public class design.wlb.studyjava.demo.restudy.c4jvm专题.chapter02.c25方法区_方法区_常量池.Client
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#21 // java/lang/Object."<init>":()V
#2 = Fieldref #22.#23 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #24 // hello world
#4 = Methodref #25.#26 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #27 // design/wlb/studyjava/demo/restudy/c4jvm专题/chapter02
/c25方法区_方法区_常量池/Client
#6 = Class #28 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Ldesign/wlb/studyjava/demo/restudy/c4jvm专题/chapter02/c25方法区_方法区
_常量池/Client;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 MethodParameters
#19 = Utf8 SourceFile
#20 = Utf8 Client.java
#21 = NameAndType #7:#8 // "<init>":()V
#22 = Class #29 // java/lang/System
#23 = NameAndType #30:#31 // out:Ljava/io/PrintStream;
#24 = Utf8 hello world
#25 = Class #32 // java/io/PrintStream
#26 = NameAndType #33:#34 // println:(Ljava/lang/String;)V
#27 = Utf8 design/wlb/studyjava/demo/restudy/c4jvm专题/chapter02/c25方法区_方法区_
常量池/Client
#28 = Utf8 java/lang/Object
#29 = Utf8 java/lang/System
#30 = Utf8 out
#31 = Utf8 Ljava/io/PrintStream;
#32 = Utf8 java/io/PrintStream
#33 = Utf8 println
#34 = Utf8 (Ljava/lang/String;)V
{
public design.wlb.studyjava.demo.restudy.c4jvm专题.chapter02.c25方法区_方法区_常量池.Client();
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 4: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ldesign/wlb/studyjava/demo/restudy/c4jvm专题/chapter02/c25方法
区_方法区_常量池/Client;public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String hello world
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/St
ring;)V
8: return
LineNumberTable:
line 6: 0
line 7: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
MethodParameters:
Name Flags
args
}
SourceFile: "Client.java"
还挺多的,那么我刚才说了,主要部分有三部分,类的基本信息。
哪部分是类的基本信息呢?以下这一部分Classfile /C:/Users/89296/IdeaProjects/studyjava/target/classes/design/wlb/studyjava/demo/restudy/
c4jvm专题/chapter02/c25方法区_方法区_常量池/Client.class 【类的文件】
Last modified 2022-6-25; size 729 bytes 【类的修改时间】
MD5 checksum 957a44a38b4099f657d2703f4cd0fce1 【签名】
Compiled from "Client.java" 【类的源java文件】
public class design.wlb.studyjava.demo.restudy.c4jvm专题.chapter02.c25方法区_方法区_常量池.Client 【类的访问修饰符、包名、类名】
minor version: 0 【类的版本】
major version: 52 【52是内部版本对应jdk1.8】
flags: ACC_PUBLIC, ACC_SUPER 【类的访问修饰符】
再往下看,从这儿开始constant pool,这就是我们说的常量池,常量池里面都是一些地址,再加一些符号,待会再解释。我们先往下看类的方法定义。
Constant pool:
#1 = Methodref #6.#21 // java/lang/Object."<init>":()V
#2 = Fieldref #22.#23 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #24 // hello world
#4 = Methodref #25.#26 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #27 // design/wlb/studyjava/demo/restudy/c4jvm专题/chapter02
/c25方法区_方法区_常量池/Client
#6 = Class #28 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Ldesign/wlb/studyjava/demo/restudy/c4jvm专题/chapter02/c25方法区_方法区
_常量池/Client;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 MethodParameters
#19 = Utf8 SourceFile
#20 = Utf8 Client.java
#21 = NameAndType #7:#8 // "<init>":()V
#22 = Class #29 // java/lang/System
#23 = NameAndType #30:#31 // out:Ljava/io/PrintStream;
#24 = Utf8 hello world
#25 = Class #32 // java/io/PrintStream
#26 = NameAndType #33:#34 // println:(Ljava/lang/String;)V
#27 = Utf8 design/wlb/studyjava/demo/restudy/c4jvm专题/chapter02/c25方法区_方法区_
常量池/Client
#28 = Utf8 java/lang/Object
#29 = Utf8 java/lang/System
#30 = Utf8 out
#31 = Utf8 Ljava/io/PrintStream;
#32 = Utf8 java/io/PrintStream
#33 = Utf8 println
#34 = Utf8 (Ljava/lang/String;)V
我们先来看第三部分类的方法定义
{
public design.wlb.studyjava.demo.restudy.c4jvm专题.chapter02.c25方法区_方法区_常量池.Client(); 【第一个是咱们的构造方法】
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1 【虚拟机指令0 1 4】
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 4: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ldesign/wlb/studyjava/demo/restudy/c4jvm专题/chapter02/c25方法区_方法区_常量池/Client;public static void main(java.lang.String[]); 【第二个是咱们的main方法】
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1 【虚拟机指令0 3 5】
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; (获取一个静态变量) ---加载了#2 【System.out】 静态变量
3: ldc #3 // String hello world (加载一个参数)加载了#3 【hello world】参数
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/St
ring;)V (执行一次虚方法调用) 执行了#4 [print方法]
8: return (执行return) 表示main方法执行结束了。
LineNumberTable:
line 6: 0
line 7: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
MethodParameters:
Name Flags
args
}
SourceFile: "Client.java"这个方法的定义就包括了虚拟机的指令了,
所以java源代码变成虚拟机指令就是这么几条指令(main方法)
0: getstatic #2 // 略
3: ldc #3 // 略
5: invokevirtual #4 // 略
8: return
上面虚拟机指令后面有一些注释,其实最后面这些注释啊是我们的javap程序,帮我们加上的。真正在cpu,在我们之前讲过java 有一个解释器,它去执行这些翻译这些虚拟机指令的时候,它看到的只有没加注释的这几行指令。
0: getstatic #2
3: ldc #3
5: invokevirtual #4
8: return
那这个解释器怎么解释呢?
它在解释的过程中,它就要把后面这个#2、#3、#4真正进行一个查表翻译.
那举个例子吧,比如说我们这个getstatic #2,这个井号二到底代表啥呢?
我要获取哪个静态变量呢?这就得去查那个常量池的表
Constant pool:
#2 = Fieldref #22.#23 // java/lang/System.out:Ljava/io/PrintStream;
【#2 是FieldReference】 它去引用了一个成员变量,引用的是哪一个成员变量呢?它又找到了#22.#23 ,#22是一个类名,我要找哪个类的成员变量呢? #29 Class java/lang/System,噢就是 java/lang/System这个类的成员变量,哪个成员变量呢?#23
引用了#30:#31 #30 out变量 #31 Ljava/io/PrintStream 对out的一个类型说明;
#22 = Class #29
#23 = NameAndType #30:#31
#29 = Utf8 java/lang/System
#30 = Utf8 out
#31 = Utf8 Ljava/io/PrintStream;
#32 = Utf8 java/io/PrintStream
#33 = Utf8 println
#34 = Utf8 (Ljava/lang/String;)V所以回到刚才的#2,我们就知道了 其实是 #29.#30:#31
也就是 java/lang/System.out:Ljava/io/PrintStream;
getstatic 就是找某个类的成员变量,就找到了System类下面的(printStream类型的)out变量。
上面读懂了,我们继续来看看
ldc #3 【ldc找到一个引用地址】,找#3的引用地址
Constant pool:
#3 = String #24
#24 = Utf8 hello world 【是一个utf8的字符串hello world,这里的utf8是虚拟机常量池中的一种类型。就把它理解为一个字符串吧】
把hello world变成一个字符串对象,加载进来(ldc) ----- ldc hello world
再往下走,就是invokevirtual
invokevirtual #4 【invokevirtual执行一次虚方法调用】,执行#4的虚方法调用。
Constant pool:
#4 = Methodref #25.#26 // java/io/PrintStream.println:(Ljava/lang/String;)V.
【Methodref 是一个方法引用】
#25 = Class #32 // java/io/PrintStream
#26 = NameAndType #33:#34 // println:(Ljava/lang/String;)V#32 = Utf8 java/io/PrintStream
#33 = Utf8 println
#34 = Utf8 (Ljava/lang/String;)V即 #4 --> #25.#26 ---> #32.#33:#34 invokevirtual #32.#33:#34 PrintStream.println
噢执行了一次,PrintStream中的println方法。
所以常量池的作用就是给我们这些指令提供一些常量符号,根据这个常量符号,以查表的方式,去找到它们,这样你的虚拟机指令才能成功的去执行。