前言
在 JAVA-运行时数据区 那章中,出现了两个常量池,分别是 运行时常量池和字符窜长量池,其实还有个class文件常量池。以下内容只是综合参考文献以及自己的理解(可能还是错误的)
class 文件常量池
首先我们先分析java源文件以及它对应的class文件
public class App {
public static void main(String[] args) throws InterruptedException {
int age = 10;
User user = new User();
user.setAge(age);
user.getAge();
}
public void test(){
String a = new String("abc");
String b = "abc";
}
}
利用javap生成的字节码文件:
Classfile /D:/idea/jg/mytest/target/classes/org/example/App.class
Last modified 2021-8-12; size 807 bytes
MD5 checksum c0126011147c7f564677908c9b3bebcc
Compiled from "App.java"
public class org.example.App
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #10.#34 // java/lang/Object."<init>":()V
#2 = Class #35 // org/example/User
#3 = Methodref #2.#34 // org/example/User."<init>":()V
#4 = Methodref #2.#36 // org/example/User.setAge:(I)V
#5 = Methodref #2.#37 // org/example/User.getAge:()I
#6 = Class #38 // java/lang/String
#7 = String #39 // abc
#8 = Methodref #6.#40 // java/lang/String."<init>":(Ljava/lang/String;)V
#9 = Class #41 // org/example/App
#10 = Class #42 // java/lang/Object
#11 = Utf8 <init>
#12 = Utf8 ()V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 LocalVariableTable
#16 = Utf8 this
#17 = Utf8 Lorg/example/App;
#18 = Utf8 main
#19 = Utf8 ([Ljava/lang/String;)V
#20 = Utf8 args
#21 = Utf8 [Ljava/lang/String;
#22 = Utf8 age
#23 = Utf8 I
#24 = Utf8 user
#25 = Utf8 Lorg/example/User;
#26 = Utf8 Exceptions
#27 = Class #43 // java/lang/InterruptedException
#28 = Utf8 test
#29 = Utf8 a
#30 = Utf8 Ljava/lang/String;
#31 = Utf8 b
#32 = Utf8 SourceFile
#33 = Utf8 App.java
#34 = NameAndType #11:#12 // "<init>":()V
#35 = Utf8 org/example/User
#36 = NameAndType #44:#45 // setAge:(I)V
#37 = NameAndType #46:#47 // getAge:()I
#38 = Utf8 java/lang/String
#39 = Utf8 abc
#40 = NameAndType #11:#48 // "<init>":(Ljava/lang/String;)V
#41 = Utf8 org/example/App
#42 = Utf8 java/lang/Object
#43 = Utf8 java/lang/InterruptedException
#44 = Utf8 setAge
#45 = Utf8 (I)V
#46 = Utf8 getAge
#47 = Utf8 ()I
#48 = Utf8 (Ljava/lang/String;)V
{
public org.example.App();
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/example/App;
public static void main(java.lang.String[]) throws java.lang.InterruptedException;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: bipush 10
2: istore_1
3: new #2 // class org/example/User
6: dup
7: invokespecial #3 // Method org/example/User."<init>":()V
10: astore_2
11: aload_2
12: iload_1
13: invokevirtual #4 // Method org/example/User.setAge:(I)V
16: aload_2
17: invokevirtual #5 // Method org/example/User.getAge:()I
20: pop
21: return
LineNumberTable:
line 9: 0
line 10: 3
line 11: 11
line 12: 16
line 13: 21
LocalVariableTable:
Start Length Slot Name Signature
0 22 0 args [Ljava/lang/String;
3 19 1 age I
11 11 2 user Lorg/example/User;
Exceptions:
throws java.lang.InterruptedException
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=1
0: new #6 // class java/lang/String
3: dup
4: ldc #7 // String abc
6: invokespecial #8 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1
10: ldc #7 // String abc
12: astore_2
13: return
LineNumberTable:
line 16: 0
line 17: 10
line 18: 13
LocalVariableTable:
Start Length Slot Name Signature
0 14 0 this Lorg/example/App;
10 4 1 a Ljava/lang/String;
13 1 2 b Ljava/lang/String;
}
SourceFile: "App.java"
SourceFile: "App.java" 利用javap生成的字节码文件:
Classfile /D:/idea/jg/mytest/target/classes/org/example/User.class
Last modified 2021-8-11; size 461 bytes
MD5 checksum f0f9061648604d27f0b37a1cd91d46ed
Compiled from "User.java"
public class org.example.User
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#21 // org/example/User.age:I
#3 = Class #22 // org/example/User
#4 = Class #23 // java/lang/Object
#5 = Utf8 age
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lorg/example/User;
#14 = Utf8 getAge
#15 = Utf8 ()I
#16 = Utf8 setAge
#17 = Utf8 (I)V
#18 = Utf8 SourceFile
#19 = Utf8 User.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = NameAndType #5:#6 // age:I
#22 = Utf8 org/example/User
#23 = Utf8 java/lang/Object
{
public org.example.User();
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 5: 0
line 6: 4
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lorg/example/User;
public int getAge();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field age:I
4: ireturn
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lorg/example/User;
public void setAge(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #2 // Field age:I
5: return
LineNumberTable:
line 11: 0
line 12: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lorg/example/User;
0 6 1 age I
}
SourceFile: "User.java"
这个就是class文件的内容,具体的文件结构内容可以参考
jvm指令手册
深入浅出JVM之Class文件结构
其中 Constant pool 就是class文件的常量池,常量池是class文件中最为重要的部分,因为其他的部分如方法、字段都会引用常量池的内容,也叫静态常量池,在编译阶段生成。主要存储字面量、常量、符号引用
字面量
前面说过,运行时常量池中主要保存的是字面量和符号引用,那么到底什么字面量?
在计算机科学中,字面量(literal)是用于表达源代码中一个固定值的表示法(notation)。几乎所有计算机编程语言都具有对基本值的字面量表示,诸如:整数、浮点数以及字符串;而有很多也对布尔类型和字符类型的值也支持字面量表示;还有一些甚至对枚举类型的元素以及像数组、记录和对象等复合类型的值也支持字面量表示法。
以上是关于计算机科学中关于字面量的解释,并不是很容易理解。说简单点,字面量就是指由字母、数字等构成的字符串或者数值。
字面量只可以右值出现,所谓右值是指等号右边的值,如:int a=123这里的a为左值,123为右值。在这个例子中123就是字面量。
int a = 123;
String s = "hollis";复制ErrorOK!
上面的代码事例中,123和hollis都是字面量。
本文开头的HelloWorld代码中,Hollis就是一个字面量。
符号引用
常量池中,除了字面量以外,还有符号引用,那么到底什么是符号引用呢。
符号引用是编译原理中的概念,是相对于直接引用来说的。主要包括了以下三类常量: * 类和接口的全限定名 * 字段的名称和描述符 * 方法的名称和描述符
这也就可以印证前面的常量池中还包含一些com/hollis/HelloWorld、main、([Ljava/lang/String;)V等常量的原因了
注意:
但并不是所有的字面量都被存到常量池中:
比如:
int i = 1;
对应的jvm指令:
0: iconst_1 // 直接将数字1压栈
1: istore_1 // 弹出栈顶元素复值给 本地变量表下标为1的变量
为什么会出现这种情况呢?对于整数字面量来说,如果值在 -32768~32767 都会直接嵌入指令中,而不会保存在常量区。
但是如果使用 final 修饰变量,将其定义成类常量(注意不是在方法体内定义的局部常量),结果又有所不同,如下:
public class JgCrmWebApplication {
final int a = 1;
public static void main(String[] args) {
int i = 1;
}
}
则在生成的class文件中能找到 ConstantValue: int 1
运行时常量池
JVM 的类加载过程(加载、连接和初始化),如下图,将 .class 文件中的静态常量池转换为方法区的运行时常量池发生在“Loading”阶段,而符号引用替换为直接引用发生在 “Resolution”阶段
当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。关于类的创建和动态连接的内容,在虚拟机类加载过程时再进行详细讲解
字符窜常量池
由于字符串池是虚拟机层面的技术,所以在 String 的类定义中并没有类似 IntegerCache 这样的对象池,String 类中提及缓存/池的概念只有intern() 这个方法,而intern() 是一个native 的方法,那么说明它本身并不是由 Java 语言实现的,而是通过 jni (Java Native Interface)调用了其他语言(如C/C++)实现的一些外部方法(具体实现需要可以自己去看看),再字符窜常量池中,存的是各个字符窜的引用。
String.intern()
判断这个常量是否存在于常量池
如果存在
判断存在内容是引用还是常量,
如果是引用,
返回引用地址指向堆空间对象,
如果是常量,
直接返回常量池常量
如果不存在,
将当前对象引用复制到常量池,并且返回的是当前对象的引用
如代码:
String s1 =new String("he") + new String("llo");
String s2 = s1.intern();
在堆中的表示:
训练:
String a1 = "AA";
System.out.println(a1 == a1.intern());
String a2 = new String("B") + new String("B");
a2.intern();
String a3 = new String("B") + new String("B");
System.out.println(a2 == a3.intern());
System.out.println(a3 == a3.intern());
问题
位于字符窜长量池的对象是否会被垃圾回收?