当前位置: 首页 > article >正文

JVM中常量池和运行时常量池、字符串常量池三者之间的关系

文章目录

  • 前言
  • 常量池(Constant Pool)
  • 运行时常量池(Runtime Constant Pool)
  • 字符串常量池(String Literal Pool)
  • 运行时常量池和字符串常量池位置变化
  • 方法区与永久代和元空间的关系
  • 三者之间的关系
    • 常量池与运行时常量池
    • 运行时常量池与字符串常量池
  • 总结


前言

在Java虚拟机(JVM)中,常量池、运行时常量池和字符串常量池是三个相关但又有所区别的概念。下面详细解释这三个概念及其相互的联系。

常量池(Constant Pool)

常量池也被称为 Class 文件常量池,是指每个 Java 类编译后的 .class 文件中的一个表结构,每个类文件(.class 文件)都有一个独立的常量池(Constant Pool)。

它存储了类或接口在编译时已知的各种字面量和符号引用。这些信息包括但不限于:

  • 字面量:如整数、浮点数、字符串等。
  • 符号引用:如类和接口的全限定名、字段名称和描述符、方法名称和描述符等。这些符号引用在类加载过程中会被解析为直接引用,从而让 JVM 能够准确地定位和调用相关的类、字段和方法。

每个类或接口都有其对应的常量池,它是类加载过程中不可或缺的一部分,用于解析类中的各种引用。

特点:常量池是静态的,在编译阶段就已经确定,存储在 .class 文件中,它是 JVM 后续加载和使用类的重要数据基础。

代码

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}

通过反编译查看以上代码的.class文件
输入javap -v HelloWorld.class查看类文件的常量池内容:

Classfile /D:/Test/jvm/out/production/jvm/cn/qf/HelloWorld.class
  Last modified 2024-12-6; size 567 bytes
  MD5 checksum 8efebdac91aa496515fa1c161184e354
  Compiled from "HelloWorld.java"
public class cn.qf.HelloWorld
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            // hello world
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // cn/qf//HelloWorld
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcn/qf//HelloWorld;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               HelloWorld.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               hello world
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               cn/qf/HelloWorld
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public cn.qf.HelloWorld();
    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 4: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcn/qf/HelloWorld;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello world
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 6: 0
        line 7: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"

重点观察Constant pool

运行时常量池(Runtime Constant Pool)

运行时常量池是每一个类或接口的常量池表的一个运行时表示形式,运行时常量池是方法区的一部分。

在Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有用于存放编译期生成的各种字面量(Literal)和符号引用(Symbolic Reference)的 常量池表(Constant Pool Table) 。

当JVM加载一个类文件时,会将该类的常量池信息(常量池表)加载到方法区内的运行时常量池中。

字符串常量池(String Literal Pool)

字符串常量池是专门用于存放程序中使用String字面量创建的字符串对象的一个特殊区域。

在JDK 6及之前,字符串常量池是运行时常量池的一部分,而JDK 7及之后字符串常量池被移到堆中,因此字符串常量池不再是严格意义上的运行时常量池的一部分。

字符串常量池特别针对字符串进行了优化设计,以实现字符串共享,减少内存占用。
它是一个类似于哈希表(HashTable)的数据结构,在 HotSpot 虚拟机中由StringTable 类实现。

  • 作用:节省内存空间,提高性能。
  • 位置:与运行时常量池类似,在JDK 7之前位于永久代,在JDK 7及之后被移到了堆中。

运行时常量池和字符串常量池位置变化

JDK1.7 之前,方法区的具体实现是永久代(Permanent Generation),运行时常量池和字符串常量池存放在方法区(永久代)。
在这里插入图片描述
JDK 7 开始,字符串常量池和静态变量从永久代中移动到了 Java 堆中,运行时常量池不变。方法区仍然由永久代实现。

这一改变主要是为了避免永久代的内存溢出问题,因为永久代的空间相对较小,且难以进行调优,而堆的管理相对更加灵活。
在这里插入图片描述
JDK 8 及以后的版本中,永久代被元空间(Metaspace)所取代(运行时常量池还是在方法区中),但字符串常量池仍然在堆中。
元空间使用的是本地内存,不再受 JVM 堆内存的限制。方法区由元空间实现,它主要存储类的元数据信息,而字符串常量池独立于元空间,在堆中进行管理。

方法区与永久代和元空间的关系

方法区和永久代以及元空间的关系很像 Java 中接口和类的关系,类实现了接口,这里的类就可以看作是永久代和元空间,接口可以看作是方法区,也就是说永久代以及元空间是 HotSpot 虚拟机对虚拟机规范中方法区的两种实现方式。
并且,永久代是 JDK 1.8 之前的方法区实现,JDK 1.8 及以后方法区的实现变成了元空间。
在这里插入图片描述

三者之间的关系

常量池与运行时常量池

常量池是 .class 文件中的静态数据结构,而运行时常量池是常量池在运行时的内存表示。
当类被加载到 JVM 中时,JVM 会将 .class 文件中的常量池信息复制到运行时常量池中,并且对符号引用进行解析和转换。

可以说,运行时常量池是常量池在运行时的具体体现,二者是静态与动态的关系。

运行时常量池与字符串常量池

  • 运行时常量池包含了多种类型的常量,而字符串常量池专门用于存储字符串常量。
  • 字符串常量池利用运行时常量池的框架,实现了字符串的共享和复用,以节省内存。
  • 在JDK 6及之前,字符串常量池是运行时常量池的一部分,而JDK 7及之后字符串常量池被移到堆中,因此字符串常量池不再是严格意义上的运行时常量池的一部分。
  • 运行时常量池中存储的是字符串常量的引用,而不是字符串对象本身。字符串对象的实际内容存储在堆中的字符串常量池。
  • 例如,当一个类文件中定义了字符串常量 “hello” 时:
    • 运行时常量池中会存储一个指向堆中字符串常量池的引用。
    • 堆中的字符串常量池会存储实际的 String 对象。

例如:

String str1 = "hello";
String str2 = "hello";

在 JDK 7 及以后,当第一次遇到 “hello” 时,会在堆中的字符串常量池创建 “hello” 字符串对象,运行时常量池记录其引用;

第二次遇到 “hello” 时,发现字符串常量池已有该对象,运行时常量池直接复用这个引用,所以 str1 和 str2 引用的是同一个堆中的字符串对象。

总结

  • 常量池是.class文件的一部分(每个class文件都有自己独立的常量池),提供了类或接口在编译时所需的各种常量信息。
  • 运行时常量池是JVM方法区中的一部分,当JVM加载一个类文件时,会将该类的常量池信息(常量池表)加载到方法区内的运行时常量池中。
  • 字符串常量池特别针对字符串进行了优化设计,以实现字符串共享,减少内存占用。它是一个类似于哈希表(HashTable)的数据结构,在 HotSpot 虚拟机中由 StringTable 类实现。
  • JDK 1.7之后运行时常量池在方法区中,字符串常量池在堆中。因此在JDK 6及之前,字符串常量池是运行时常量池的一部分,而JDK 7及之后字符串常量池被移到堆中,因此字符串常量池不再是严格意义上的运行时常量池的一部分。
  • 运行时常量池中存储的是字符串常量的引用,而不是字符串对象本身。字符串对象的实际内容存储在堆中的字符串常量池

原文地址:https://blog.csdn.net/weixin_46425661/article/details/146229481
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/590466.html

相关文章:

  • JVM常用概念之安全点轮询
  • 验证哥德巴赫猜想(C语言)
  • Go红队开发—日志打印优化
  • 基于“动手学强化学习”的知识点(二):第 15 章 模仿学习(gym版本 >= 0.26)
  • A l密码学(Deepseek)
  • [Windows] 轻量级景好鼠标录制器 v2.1 单文件版,支持轨迹+鼠标键盘录制复刻
  • es6什么是暂时性死区,为何会存在
  • golang开发支持onlyoffice的token功能
  • 2025-3-17算法打卡
  • 02 javase面向对象-狂神说课程笔记
  • 自学Python创建强大AI:从入门到实现DeepSeek级别的AI
  • 多任务学习与持续学习微调:深入探索大型语言模型的性能与适应性
  • 便携版:随时随地,高效处理 PDF 文件
  • matlab 火电厂给水控制系统仿真
  • linux(centos8)下编译ffmpeg
  • AndroidStudio+Android8.0下的Launcher3 导入,编译,烧录,调试
  • K8S学习之基础三十三:K8S之监控Prometheus部署程序版
  • 深度学习项目--基于DenseNet网络的“乳腺癌图像识别”,准确率90%+,pytorch复现
  • 基于YOLOv8与SKU110K数据集实现超市货架物品目标检测与计算
  • 4-001:MySQL 中的索引数量是否越多越好?为什么?