1 字符串常量池

频繁的创建字符串,会极大的影响程序的性能。为了提高性能和减少内存消耗,JVM开辟并维护一个字符串常量池,作用类似于缓冲区。在创建字符串时,首先检查字符串常量池中是否存在字符串。

  • 如果存在,则返回字符串常量池中该字符串的引用。
  • 如果不存在,则创建该字符串,并放入字符串常量池中。

在 HotSpotVM 里实现的字符串常量池功能的是一个 StringTable 类,它是一个 Hash 表,默认值大小长度是 1009 ;这个 StringTable 在每个 HotSpot-VM 的实例中只有一份,被所有的类共享。
字符串常量由一个一个字符组成,放在了 StringTable 上。在 JDK6 中,StringTable 的长度是固定的,长度就是 1009,因此如果放入 String Pool 中的 String 非常多,就会造成 hash 冲突,导致链表过长,当调用 String#intern() 时会需要到链表上一个一个找,从而导致性能大幅度下降。
在 JDK7 中,StringTable 的长度可以通过参数指定:-XX:StringTableSize=66666。

2 静态常量池

也称为class常量池。Java 类被编译后,就会形成一份 class 文件; class 文件中除了包含类的版本、成员变量、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。每个 class 文件都会有一个 class 常量池。
关于class文件的内部结构,将在JVM中做进一步介绍。

3 运行时常量池

运行时常量池是方法区的一部分,存在于内存中,也就是 class 常量池被加载到内存之后的版本。Java虚拟机规范没有对其做任何要求,不同虚拟机可以根据自己的需求对其做具体的实现。当class文件被加载后,它的静态常量池信息就会放入运行时常量池,并把里面的符号引用替换成直接引用。
另外,运行时常量池有一个重要的特征:动态性。编译期的常量池中的内容可以放入运行时常量池,运行时产生的常量也可以放入池中。常见的有String类intern()方法。
运行时常量池会受到方法区内存的限制,当无法申请到内存时会抛出OOM(OutOfMemoryError)异常。

4 三种常量池的关系

JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。
当类加载到内存中后,jvm 就会将 静态常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。
在解析阶段,会把符号引用替换为直接引用,解析的过程会去查询字符串常量池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。

5 三种常量池的随JDK版本的变化

  • 静态常量池一直在class文件中。、

  • 运行时常量池和字符串常量池:

    • JDK7之前,运行时常量池包括字符串常量池都存放在方法区中,此时HotSpotVM 对方法区的实现是永久代。

20200830195642170.jpg

  • JDK1.7,字符串常量池和静态变量被从方法区拿到了堆内存中,运行时常量池剩下的部分还在方法区中,此时HotSpotVM 对方法区的实现是永久代。

20200830195653168.jpg

  • JDK8,HotSpotVM 移除了永久代,用元空间取而代之。字符串常量池还在堆中,运行时常量池还在方法区中,不过此时对方法区的实现变成了元空间。

20200830195704168.jpg

6 注意

JVM中会对常量池做进一步的介绍,这里只是对其进行简单了解。

参考