首先,理解下啥子叫常量池, 常量池是一个放入数据的一个堆(JDK1.8中), 如果是重新创建一个相同的数据,则把引用指向常量池. 大致可以这么理解.
常量池要划分的话,那么可以划分为静态常量池和运行时常量池。注意:静态常量池再运行时也会写入到运行时的常量池
静态常量池: 指的是再编译的过程中,就能确定下来值的,都可以称为静态常量池,class常量池不仅仅包括字符和数字这些/字面量。比如我们用final 修饰的基本类型,或者用String s1 = "a"; 等 看如下代码:
package com.jvm.classloading;
public class MyTest2 {
public static void main(String[] args) {
String str = "abc";
System.out.println(str);
System.out.println(MyParent2.str);
}
}
class MyParent2{
public static final String str = "hello word";
static {
System.out.println("MyParent2 static blok");
}
}
这段代码中,MyParent2 这个类不会初始化,因为没有设计到主动调用,static模块不会被调用.如果要了解类加载,请看我的文章
所以输出的结果为
abc
hello word
然后我们在使用javap 看下静态常量池是如何做的
Classfile /C:/Users/able/IdeaProjects/jvm/out/production/jvm/com/jvm/classloading/MyTest2.class
Last modified 2020-4-7; size 665 bytes
MD5 checksum bc4de9eac30a5e4f9ecc4029ddacf12f
Compiled from "MyTest2.java"
public class com.jvm.classloading.MyTest2
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#24 // java/lang/Object."<init>":()V
#2 = String #25 // abc
#3 = Fieldref #26.#27 // java/lang/System.out:Ljava/io/PrintStream;
#4 = Methodref #28.#29 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #30 // com/jvm/classloading/MyParent2
#6 = String #31 // hello word
#7 = Class #32 // com/jvm/classloading/MyTest2
#8 = Class #33 // java/lang/Object
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lcom/jvm/classloading/MyTest2;
#16 = Utf8 main
#17 = Utf8 ([Ljava/lang/String;)V
#18 = Utf8 args
#19 = Utf8 [Ljava/lang/String;
#20 = Utf8 str
#21 = Utf8 Ljava/lang/String;
#22 = Utf8 SourceFile
#23 = Utf8 MyTest2.java
#24 = NameAndType #9:#10 // "<init>":()V
#25 = Utf8 abc
#26 = Class #34 // java/lang/System
#27 = NameAndType #35:#36 // out:Ljava/io/PrintStream;
#28 = Class #37 // java/io/PrintStream
#29 = NameAndType #38:#39 // println:(Ljava/lang/String;)V
#30 = Utf8 com/jvm/classloading/MyParent2
#31 = Utf8 hello word
#32 = Utf8 com/jvm/classloading/MyTest2
#33 = Utf8 java/lang/Object
#34 = Utf8 java/lang/System
#35 = Utf8 out
#36 = Utf8 Ljava/io/PrintStream;
#37 = Utf8 java/io/PrintStream
#38 = Utf8 println
#39 = Utf8 (Ljava/lang/String;)V
{
public com.jvm.classloading.MyTest2();
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
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/jvm/classloading/MyTest2;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: ldc #2 // String abc
2: astore_1
3: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
6: aload_1
7: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
13: ldc #6 // String hello word
15: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
18: return
LineNumberTable:
line 6: 0
line 7: 3
line 8: 10
line 9: 18
LocalVariableTable:
Start Length Slot Name Signature
0 19 0 args [Ljava/lang/String;
3 16 1 str Ljava/lang/String;
}
SourceFile: "MyTest2.java"
我们先来看下 String str = "abc";
0: ldc #2 // String abc
找到这行jvm编译的. 这里使用的是符号引用,如果想了解符号引用,观看符号引用和直接引用
根据#2 我们最后找到
#25 = Utf8 abc
这里已经在编译的时候,就把abc给写入进去
同理在看下
System.out.println(MyParent2.str);
输出代码我们先不管, 我们看到
13: ldc #6 // String hello word
这行代码,最后的解析为
#31 = Utf8 hello word
在生成的class文件中,已经把我们定义的变量写入进去.这里我就可以理解为静态常量池。 同理 基本类型也是一样,能在运行期间确定的值,都在编译时,写入class文件中。当我们在运行时,也会把这些值写入堆内存常量池中。
-------------------------------------------------------分割线----------------------------------------------------------------------------------------------------------------------
下面我们在说下 运行时常量池
运行时常量池: 虚拟机会将在类加载后把各个class文件中的常量池载入运行时常量池中,前面的静态常量池只是一个静态文件结构,运行时常量池也是方法区的一部分(JDK1.6之前),是一块内存区域.运行时常量池可以在运行期间符号引用解析为直接引用
即把那些描述符替换为能直接定位到字段,方法的引用和句柄.
后面再说说字符串常量池,整数常量池.