Memory Model -- 07 -- Java中的常量池详解

最近在学习 Java 虚拟机的相关知识时,出现了好几种常量池的概念,被弄的很模糊,于是上网查了下资料,再结合自己的看法,总结了一下:在 java 的内存分配中,总共存在三种常量,分别是:字符串常量池Class 文件常量池运行时常量池


一、字符串常量池 (String Constant Pool)

  • 在 HotSpot 虚拟机中,实现字符串常量池的是一个名为 StringTable 的类,它是一个 Hash 表,默认长度为 1009;其在每个 HotSpot 虚拟机中的实例只有一份,被所有的类所共享,字符串常量由一个个字符组成,放在了 StringTable 上

  • 在 Java6 及之前的版本中,字符串常量池位于方法区 (永久代) 中,StringTable 的长度固定为 1009,因此如果放入字符串常量池中的字符串过多,就会造成 hash 冲突,从而导致链表过长,当调用 String 类的 intern() 方法时需要到链表上一个个地查找,从而导致性能大幅度下降

  • 在 Java7 及之后版本中,字符串常量池已被移动到 Java 堆中,实际上是将字符串的实例对象存储在了 Java 堆中,而字符串实例对象的引用则存储在 StringTable 中,此外其长度可以通过以下参数指定:StringTableSize

    -XX:StringTableSize=1009
    

二、Class 文件常量池 (Class Constant Pool)

  • Class 文件除了包含了类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池 (Constant Pool Table),也就是我们所说的 Class 文件常量池 (Class Constant Pool),也称为静态常量池

  • 每个 Class 文件都会有一个自己对应的 Class 文件常量池,用于存放编译期生成的各种字面量 (Literal) 和符号引用 (Symbolic References)

    • 字面量:包括文本字符串、基本数据类型的值、被声明为 final 的常量等

    • 符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可

      • 类和接口的全限定名

      • 字段的名称和描述符

      • 方法的名称和描述符

  • Class 文件常量池中的内容将会在类被加载到虚拟机内存后进入方法区的运行时常量池中存放


三、运行时常量池 (Runtime Constant Pool)

  • 运行时常量池 (Runtime Constant Pool) 是方法区 (Java8 之前是永久代,Java8 开始是元空间) 的一部分, 当类被加载到虚拟机内存中,会经历加载、连接 (验证、准备、解析)、初始化三个阶段,此时 虚拟机会将 Class 文件常量池中的内容存放到运行时常量池中

  • Class 文件常量池中存的是字面量和符号引用,在类加载的解析阶段,虚拟机会将 Class 文件常量池内的符号引用替换为直接引用,也就是说运行时常量池不仅保存了符号引用,还保存了由符号引用转换得到的直接引用。在解析的过程中会去查询字符串常量池,也就是我们上面提到的 StringTable,以保证运行时常量池所引用的字符串与字符串常量池中的是一致的

    • 直接引用:可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄
  • 运行时常量池相对于 Class 文件常量池来说,另外一个重要特征是具有动态性,Java 语言并不要求常量一定只有编译器才能产生,也就是并非只有 Class 文件常量池中的内容才能进入运行时常量池,运行期间也可以将新的常量放入池中,这种特性使用的较多的是 String 类的 intern() 方法

  • 运行时常量池是方法区的一部分,受到方法区内存的限制,当运行时常量池无法再申请到内存时会抛出 OutOfMemoryError 异常


四、归纳总结

  • 字符串常量池 (String Constant Pool)

    • 由一个名为 Stringtable 的 Hash 类实现,默认长度为 1009

    • 在每个虚拟机中只有一份实例,被所有的类所共享

    • 存放位置

      • Java 6 及之前,位于方法区 (永久代) 中

      • Java 7 及之后,位于 Java 堆中

        • 将字符串的实例对象存储在 Java 堆中,将字符串实例对象的引用存储在 StringTable 中
  • Class 文件常量池 (Class Constant Pool)

    • 也称为:静态常量池

    • 用于存放编译期生成的各种字面量和符号引用

    • 当类被加载到虚拟机内存后,虚拟机会将 Class 文件常量池中的内容存放到运行时常量池中

  • 运行时常量池 (Runtime Constant Pool)

    • 具有动态性,在运行期间可以将新的常量放入池中,如:使用 String 类的 intern() 方法

五、参考资料

发布了106 篇原创文章 · 获赞 83 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/Goodbye_Youth/article/details/103339876