前言
java中有字节码中的常量池、运行时常量池、字符串常量池三张常量池,语雀地址:https://www.yuque.com/yangxiaofei-vquku/wmp1zm/dmz2gd为甚要有常量池
public class Demo1 {
public static void main(String[] args) {
System.out.println("123");
System.out.println(UUID.randomUUID().toString());
}
}
一个.java文件只有几行代码,但其加载时却需要依赖很多东西,像Objec类、System、UUID、String、toString()方法、println()方法等,这些信息不可能全部编译到对应的.class文件中,也不能全部方到Demo1的元数据中,所以就引入了常量池这个概念,常量池可以看做是一张表,虚拟机指令可以根据这张表找到要执行的类、方法、属性、参数类型、返回值类、字面量型等引用信息。
字节码中的常量池
将一个.class文件用javap命令反编译后,发现生成的文件中最大的结构叫Constant pool这就是我们所说的字节码中的常量池,其主要内容是字面量(文本字符串、声明为final的常量值)、符号引用(类和接口的全限定名、字段的名称和描述符、方法的名称和描述符)。
列:
Constant pool:
#1 = Methodref #13.#47 // java/lang/Object."<init>":()V
#2 = String #48 // String
#3 = Fieldref #6.#49 // jvm/memory/loading/Person.a:Ljava/lang/String;
#4 = Fieldref #6.#50 // jvm/memory/loading/Person.b:I
#5 = Fieldref #6.#51 // jvm/memory/loading/Person.finalStr1:Ljava/lang/String;
#6 = Class #52 // jvm/memory/loading/Person
#7 = String #53 // final String
#8 = Fieldref #6.#54 // jvm/memory/loading/Person.staticStr:Ljava/lang/String;
#9 = Fieldref #6.#55 // jvm/memory/loading/Person.staticInt:I
#10 = Methodref #56.#57 // java/util/UUID.randomUUID:()Ljava/util/UUID;
#11 = Methodref #56.#58 // java/util/UUID.toString:()Ljava/lang/String;
#12 = String #59 // static String
#13 = Class #60 // java/lang/Object
#14 = Utf8 finalStr1
#15 = Utf8 Ljava/lang/String;
#16 = Utf8 finalStr2
#17 = Utf8 ConstantValue
#18 = Utf8 staticStr
#19 = Utf8 staticInt
#20 = Utf8 I
#21 = Utf8 a
#22 = Utf8 b
#23 = Utf8 <init>
#24 = Utf8 ()V
#25 = Utf8 Code
#26 = Utf8 LineNumberTable
#27 = Utf8 LocalVariableTable
#28 = Utf8 this
#29 = Utf8 Ljvm/memory/loading/Person;
#30 = Utf8 getFinalStr1
#31 = Utf8 ()Ljava/lang/String;
#32 = Utf8 getFinalStr2
#33 = Utf8 getStaticStr
#34 = Utf8 setStaticStr
#35 = Utf8 (Ljava/lang/String;)V
#36 = Utf8 getStaticInt
#37 = Utf8 ()I
#38 = Utf8 setStaticInt
#39 = Utf8 (I)V
#40 = Utf8 getA
#41 = Utf8 setA
#42 = Utf8 getB
#43 = Utf8 setB
#44 = Utf8 <clinit>
#45 = Utf8 SourceFile
#46 = Utf8 Person.java
#47 = NameAndType #23:#24 // "<init>":()V
#48 = Utf8 String
#49 = NameAndType #21:#15 // a:Ljava/lang/String;
#50 = NameAndType #22:#20 // b:I
#51 = NameAndType #14:#15 // finalStr1:Ljava/lang/String;
#52 = Utf8 jvm/memory/loading/Person
#53 = Utf8 final String
#54 = NameAndType #18:#15 // staticStr:Ljava/lang/String;
#55 = NameAndType #19:#20 // staticInt:I
#56 = Class #61 // java/util/UUID
#57 = NameAndType #62:#63 // randomUUID:()Ljava/util/UUID;
#58 = NameAndType #64:#31 // toString:()Ljava/lang/String;
#59 = Utf8 static String
#60 = Utf8 java/lang/Object
#61 = Utf8 java/util/UUID
#62 = Utf8 randomUUID
#63 = Utf8 ()Ljava/util/UUID;
#64 = Utf8 toString
什么是符号引用
以一组符号来表示被引用的目标,符号可以是任何新式的字面量,只要能无歧义的表示被引用目标即可,符号引用于jvm的内存布局无关,被引用的目标并不一定被加载到内存。
运行时常量池
当.class文件被加载后,字节码的各部分信息都会被解析转换池类的元数据信息保存在方法区,当然也包括字节码中的常量池(在jvm中一个类对应一个运行时常量池)。虚拟机会将字节码常量池中的符号引用转换为直接引用,并翻译到具体的内存地址中。
什么是直接引用
直接引用即直接执行引用目标的指针、相对偏移量或者一个能间接定位到目标的句柄,直接引用是与jvm的内存内存布局相关的,如果有了直接引用说明引用的目标已经被加载到内存中了,如果尚未开辟内存空间则无法获取直接引用。
字符串常量池
字符串常量池在jdk1.6是位于方法区(永久带),在jdk1.7以后移动到了堆中,字符串常量池整个jvm进程只有一份,保存了全部字符串的直接引用。运行时常量池每个类都有一份,其中在解析运行时常量池中字符串字面量的直接引用时会和全局的字符串常量池做比较保证一致。
字符串常量池为什么要移动到堆中
原先字符串常量池在方法区(永久带)中,由于方法区只有在full GC时才会被回收,full GC只有在方法区或者老年代满了时才会触发,一个系统中会用到大量字符串,继而导致字符串常量池中很多无用字符串得不到回收,浪费了方法区的空间。所以才会将字符串常量池转移到堆中,降低GC成本,提供空间利用率。