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

server/2025/3/14 5:51:40/

文章目录

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


前言

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

常量池(Constant Pool)

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

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

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

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

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

代码

java">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.classLast modified 2024-12-6; size 567 bytesMD5 checksum 8efebdac91aa496515fa1c161184e354Compiled from "HelloWorld.java"
public class cn.qf.HelloWorldminor version: 0major version: 52flags: 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: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 4: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       5     0  this   Lcn/qf/HelloWorld;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=1, args_size=10: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc           #3                  // String hello world5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnLineNumberTable:line 6: 0line 7: 8LocalVariableTable:Start  Length  Slot  Name   Signature0       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 对象。

例如:

java">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及之后字符串常量池被移到堆中,因此字符串常量池不再是严格意义上的运行时常量池的一部分。
  • 运行时常量池中存储的是字符串常量的引用,而不是字符串对象本身。字符串对象的实际内容存储在堆中的字符串常量池


http://www.ppmy.cn/server/174795.html

相关文章

深度学习篇---Opencv中Haar级联分类器的自定义

文章目录 1. 准备工作1.1安装 OpenCV1.2准备数据集1.2.1正样本1.2.2负样本 2. 数据准备2.1 正样本的准备2.1.1步骤2.1.2生成正样本描述文件2.1.3示例命令2.1.4正样本描述文件格式 2.2 负样本的准备2.2.1步骤2.2.2负样本描述文件格式 3. 训练分类器3.1命令格式3.2参数说明 4. 训…

【农业大数据处理与应用】实验二 随机森林算法与LSTM循环神经网络

一、实验目的 1.掌握利用随机森林算法构建分类器模型的方法&#xff0c;并且了解如何准确评估随机森林模型分类器的性能&#xff0c;包括准确率、精确度、召回率和F1分数等指标的计算和解读&#xff0c;以便对模型的表现进行全面评价&#xff1b; 2.深入学习随机森林模型的参…

【Golang】第三弹----运算符

笔上得来终觉浅,绝知此事要躬行 &#x1f525; 个人主页&#xff1a;星云爱编程 &#x1f525; 所属专栏&#xff1a;Golang &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 一、运算符介绍 运算符是一…

【Aioredis实战总结】Aioredis简介

一、Aioredis简介 Aioredis 是一个基于Python asyncio框架的异步Redis客户端库&#xff0c;专为高并发场景设计。它允许开发者在不阻塞主线程的情况下执行Redis操作&#xff0c;显著提升I/O密集型任务&#xff08;如Web应用的缓存、实时消息队列等&#xff09;的性能。自4.2.0…

《基于深度学习的图像识别技术在医学影像分析中的应用研究》

## 摘要 研究探讨了基于深度学习的图像识别技术在医学影像分析中的应用。随着人工智能技术的快速发展&#xff0c;深度学习在医学影像领域展现出巨大潜力。本文首先介绍了深度学习的基本概念和常用模型&#xff0c;重点分析了卷积神经网络在图像识别中的优势。随后&#xff0c…

Redis的缓存雪崩、缓存击穿、缓存穿透与缓存预热、缓存降级

一、缓存雪崩&#xff1a; 1、什么是缓存雪崩&#xff1a; 如果缓在某一个时刻出现大规模的key失效&#xff0c;那么就会导致大量的请求打在了数据库上面&#xff0c;导致数据库压力巨大&#xff0c;如果在高并发的情况下&#xff0c;可能瞬间就会导致数据库宕机。这时候如果…

驱动开发系列46 - Linux 显卡KMD驱动代码分析(七)- 显存管理

目录 一:概述 二:应用程序和UMD调用栈 三:KMD 显存分配和和映射过程 一:概述 显存管理是图形驱动程序中至关重要的一部分,涉及到从用户空间(UMD,User Mode Driver)到内核空间(KMD,Kernel Mode Driver)的显存分配和管理。本文将首先梳理从一个 OpenGL 应…

【VS】vs生成前事件,复制脚本文件至运行目录

​ 在项目目录中添加了一些配置文件&#xff0c;或者脚本文件&#xff0c;运行时需要把这些文件复制运行目录下&#xff0c;visual studio 中 可以设置生成事件&#xff0c;项目生成时自动复制文件到运行目录。 xcopy /Y “KaTeX parse error: Undefined control sequence: \c…