JVM常量池(class文件常量池,运行时常量池,字符串常量池)

news2025/5/23 6:33:18

文章目录

  • 问题
  • JVM运行时数据区
  • JVM中的常量池
    • Class文件常量池
    • 运行时常量池
    • 字符串常量池
      • 创建了几个对象
      • String的定义
      • intern()
      • 问题

超过1W字深度剖析JVM常量池(全网最详细最有深度) - 跟着Mic学架构 - 博客园

问题

jdk1.8之后

  • 元空间是独立存在的?还是位于方法区?
  • 字符串常量池在堆中还是独立存在?

在 JDK 1.8 及之后版本:

  1. 元空间与方法区关系元空间可以理解为是方法区的一种实现 。JDK 1.8 移除了永久代,引入元空间来替代。元空间使用本地内存(Native Memory ),不再像永久代那样在 JVM 堆内存中划分一块固定区域。此时方法区的功能由元空间来承担,运行时常量池等原本在方法区的部分也存在于元空间中,但元空间和传统意义上方法区概念并非完全等同,只是功能上有继承关系。 所以说元空间是方法区新的实现形式,它不是独立于方法区概念存在,而是改变了方法区的实现方式
  2. 字符串常量池位置字符串常量池在堆中。JDK 1.7 时,字符串常量池从方法区(永久代 )迁移到堆中,JDK 1.8 延续了这一做法。在堆中的字符串常量池,用于存储字符串字面量以及通过String.intern()方法添加进去的字符串 。其物理位置位于堆中,逻辑归属于方法区,从 Java 虚拟机规范角度,它是方法区相关概念的一部分,承担着存储字符串字面量等常量的功能,和方法区其他组成部分(如运行时常量池等)有紧密逻辑关联。

JVM运行时数据区

从类加载,到JVM运行时数据区的整体结构画出来,如下图所示(元空间位于本地内存)。Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途。

JVM运行时数据区在物理内存上并不是单一的连续内存块,而是由多个不同物理来源的内存区域组合而成的逻辑概念

在这里插入图片描述

JVM中的常量池

JVM中的常量池可以分成以下几类:

  1. Class文件常量池/静态常量池
  2. (全局)字符串常量池
  3. 运行时常量池

Class文件常量池

程序编译后可以通过javap -c 类名.class查看该类的字节码文件。或者使用jclasslib插件。

在这里插入图片描述

  1. 位置:存在于编译后的.class文件中,是.class文件的一部分。

    • 在编译阶段,静态常量池在.class文件中,此时还未加载到JVM内存,纯属磁盘上的文件内容。
    • 当类被JVM加载时,静态常量池的内容会被解析并存入运行时常量池(Runtime Constant Pool)。
  2. 作用:存储编译期生成的各种字面量和符号引用。

    • 字面量:指的是在源代码中直接给出的数据值,在源代码中直接写出的数值,字符串,布尔等。比如文本字符串(String a = “Hello World”);声明为final的常量值(private int value = 1);基本数据类型的值。

    • 符号引用

      • 类和接口的全限定名。也就是Ljava/lang/String;,主要用于在运行时解析得到类的直接引用。

          #23 = Utf8               ([Ljava/lang/String;)V
          #25 = Utf8               [Ljava/lang/String;
          #27 = Utf8               Ljava/lang/String;
        
      • 字段的名称和描述符。字段也就是类或者接口中声明的变量,包括类级别变量(static)实例级的变量

      • 方法的名称和描述符。方法的描述类似于JNI动态注册时的“方法签名”,也就是参数类型+返回值类型,比如下面的这种字节码,表示main方法和String返回类型。

          #19 = Utf8               main
          #20 = Utf8               ([Ljava/lang/String;)V
        

运行时常量池

运行时常量池就是每一个类或接口的常量池(constant pool)在运行时的表现形式。

一个类在加载的过程,会经历加载连接(验证、准备、解析)初始化,而在类加载这个阶段,需要做以下几件事情:

  1. 通过一个类的全类限定名获取此类的二进制字节流。
  2. 在堆内存生成一个java.lang.Class对象,代表加载这个类,做为这个类的入口。
  3. class字节流的静态存储结构转化成方法区(元空间)的运行时数据结构。

而第三点就包含了class文件常量池进入运行时常量池的过程。

所以,运行时常量池的作用是存储class文件常量池中的符号信息,在类的解析阶段会把这些符号引用转换成直接引用(实例对象的内存地址),翻译出来的直接引用也是存储在运行时常量池中。class文件常量池的大部分数据会被加载到运行时常量池。

虽然方法区(或元空间)是全局共享的内存区域,但每个类都有自己独立的运行时常量池

运行时常量池是 动态的符号引用解析中心,其核心行为可归纳为:

  1. 存储:承载 Class 文件常量池的原始符号信息
  2. 转换:在解析阶段将符号引用替换为直接引用
  3. 扩展:支持运行时动态添加新常量
  4. 协同:与字符串常量池、方法区元数据紧密交互

字符串常量池

位置

虽然从物理位置上看,它在堆空间内,但它是一个逻辑上独立的区域。

存在意义

JVM之所以单独设计字符串常量池,是JVM为了提高性能以及减少内存开销的一些优化:

  1. String对象作为Java语言中重要的数据类型,是内存中占据空间最大的一个对象。高效地使用字符串,可以提升系统的整体性能。
  2. 创建字符串常量时,首先检查字符串常量池是否存在该字符串,如果有,则直接返回该引用实例,不存在,则实例化该字符串放入常量池中。

举例

String a="Hello";

String b=new String("Mic");
  1. a这个变量,是在编译期间就已经确定的,会进入到字符串常量池。
  2. b这个变量,是通过new关键字实例化,new是创建一个对象实例并初始化该实例,因此这个字符串对象是在运行时才能确定的,创建的实例在堆空间上。

在这里插入图片描述

创建了几个对象

深入理解Java字符串常量池 | 二哥的Java进阶之路

String s = new String("二哥");

使用 new 关键字创建一个字符串对象时,Java 虚拟机会先在字符串常量池中查找有没有‘二哥’这个字符串对象,如果有,就不会在字符串常量池中创建‘二哥’这个对象了,直接在堆中创建一个‘二哥’的字符串对象,然后将堆中这个‘二哥’的对象地址返回赋值给变量 s。

如果没有,先在字符串常量池中创建一个‘二哥’的字符串对象,然后再在堆中创建一个‘二哥’的字符串对象,然后将堆中这个‘二哥’的字符串对象地址返回赋值给变量 s。

对于这行代码 String s = new String("二哥");,它创建了两个对象:一个是字符串对象 “二哥”,它被添加到了字符串常量池中,另一个是通过 new String() 构造方法创建的字符串对象 “二哥”,它被分配在堆内存中,同时引用变量 s 存储在栈上,它指向堆内存中的字符串对象 “二哥”。

在这里插入图片描述

String的定义

public final class String

    implements java.io.Serializable, Comparable<String>, CharSequence {

    /** The value is used for character storage. */

    private final char value[];



    /** Cache the hash code for the string */

    private int hash; // Default to 0

}

从上述源码中可以发现。

  1. String这个类是被final修饰的,代表该类无法被继承。
  2. String这个类的成员属性value[]也是被final修饰,代表该成员属性不可被修改。

因此String具有不可变的特性,也就是说String一旦被创建,就无法更改。这么设计的好处有几个。

  1. 方便实现字符串常量池: 在Java中,由于会大量的使用String常量,如果每一次声明一个String都创建一个String对象,那将会造成极大的空间资源的浪费。Java提出了String pool的概念,在堆中开辟一块存储空间String pool,当初始化一个String变量时,如果该字符串已经存在了,就不会去创建一个新的字符串变量,而是会返回已经存在了的字符串的引用。如果字符串是可变的,某一个字符串变量改变了其值,那么其指向的变量的值也会改变,String pool将不能够实现!
  2. 线程安全性,在并发场景下,多个线程同时读一个资源,是安全的,不会引发竞争,但对资源进行写操作时是不安全的,不可变对象不能被写,所以保证了多线程的安全。
  3. 保证 hash 属性值不会频繁变更。确保了唯一性,使得类似HashMap容器才能实现相应的key-value缓存功能,于是在创建对象时其hashcode就可以放心的缓存了,不需要重新计算。这也就是Map喜欢将String作为Key的原因,处理速度要快过其它的键对象。所以HashMap中的键往往都使用String。

intern()

  1. JDK文档中关于intern()方法的说明:当调用intern方法时,如果常量池(内置在 JVM 中的)中已经包含相同的字符串,则返回池中的字符串。否则,将此String对象添加到池中,并返回对该String对象的引用。‘
  2. 注意,所有字符串字面量在初始化时,会默认调用intern()方法。
  3. intern() 方法的核心逻辑
    • 检查常量池:JVM 会通过内容匹配(而非引用相等)查找常量池中是否已有该字符串。
    • 处理结果
      • 若存在:返回常量池中的引用(可能指向堆中的对象)。
      • 若不存在:JDK 7 及以后直接在常量池中记录堆中对象的引用,而非复制内容(节省内存),并返回该引用。

问题

public static void main(String[] args) {

   String a = new String(new char[]{'a', 'b', 'c'});  // 堆中创建新对象,常量池未变化

   String b = a.intern();                              // 将a的内容放入常量池(如果不存在),并返回池中的引用

   System.out.println(a == b);                         // true:此时常量池中的引用就是a本身
}

1. 为什么 new String(char[]) 不会自动调用 intern()

当使用 new String(char[]) 构造函数时,JVM 会创建一个全新的String对象,但不会自动将其放入常量池。这是因为:

  • 常量池的设计初衷:常量池主要用于存储编译期已知的字符串字面量(如 "abc")或显式调用 intern() 的字符串。
  • 性能考量:如果每次通过字符数组创建字符串都自动入池,会导致常量池膨胀,影响性能和内存占用。
  • 语义一致性new 关键字的语义是 “强制创建新对象”,即使常量池中已有相同内容的字符串,new String(char[]) 也会创建独立的对象。

3. 总结:何时字符串会进入常量池?

  • 编译期已知的字面量(如 "abc")会在类加载时自动进入常量池。
  • 运行时动态创建的字符串(如通过字符数组、substring() 等方法)不会自动入池,除非显式调用 intern()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2383689.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

我爱学算法之—— 二分查找(中)

一、搜索插入位置 题目解析 这道题&#xff0c;给定一个数组nums和一个目标值target&#xff0c;让我们在数组nums中找到目标值&#xff1b;如果目标值存在就返回它的下标&#xff0c;如果不存在就返回数target被顺序插入的位置下标。 算法思路 这道题&#xff0c;我们可以使…

军事目标系列之迷彩作战人员检测数据集VOC+YOLO格式2755张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2755 标注数量(xml文件个数)&#xff1a;2755 标注数量(txt文件个数)&#xff1a;2755 …

node12.22.12在nvm中安装

1、安装nvm 官网&#xff1a;https://nvm.uihtm.com/ 下载&#xff0c;安装 nvm -v 1.2.22、通过 nvm install 12.22.12 安装报错&#xff0c;找不到此版本 通过下载 https://nodejs.org/zh-cn/downloadzip文件 解压 3、查看nvm 安装路径 nvm root4、在目录下新建文件夹 v…

【生态信息】开源软件全方位解析

开源软件(0pen Source Software&#xff0c;0ss)是指其源代码可以公开发布、查看、使用和修改的软件。这一概念的核心在于开放性和共享性&#xff0c;允许开发者自由地使用、修改、分发以及改进软件。开源软件通常遵循特定的开源许可证&#xff0c;这些许可证确保了软件的自由使…

FastAPI在 Nginx 和 Docker 环境中的部署

目录 实现示例1. 项目结构2. FastAPI 应用 (app/main.py)3. 依赖文件 (app/requirements.txt)4. Dockerfile5. Nginx 配置 (nginx/nginx.conf)6. Docker Compose 配置 (docker-compose.yml) 使用方法修改代码后更新 实现示例 接下来创建一个简单的示例项目&#xff0c;展示如何…

计算机网络相关面试题

一、HTTP1.1和HTTP2的区别 HTTP/1&#xff08;主要指 HTTP/1.1&#xff09;和 HTTP/2 是 Web 协议发展中的两个重要版本&#xff0c;二者在性能、协议机制和功能特性上有显著差异。以下从多个维度对比分析&#xff0c;并结合具体案例说明&#xff1a; 一、连接与请求处理方式 1…

根据当前日期计算并选取上一个月和上一个季度的日期范围,用于日期控件的快捷选取功能

1.选择月份范围 代码如下&#xff1a; <el-date-picker v-model"value" type"monthrange" align"right" unlink-panels range-separator"至"start-placeholder"开始月份" end-placeholder"结束月份" :picker-…

【C++】set、map 容器的使用

文章目录 1. set 和 multiset 的使用1.1 set类的介绍1.2 set的构造和迭代器1.3 set 的增删查1.4 insert和迭代器调用示例1.5 find和erase使用示例1.6 multiset和set的差异 2. map 和 multimap 的使用2.1 map 类的介绍2.2 pair 类型介绍2.3 map 的构造和迭代器2.4 map 的增删查2…

【MySQL】第1节|全面理解MySQL架构

快速安装MySQL 使用Docker快速安装mysql8 docker run -d \ --name mysql8 \ --privilegedtrue \ --restartalways \ -p 13306:3306 \ -v /home/mysql8/data:/var/lib/mysql \ -v /home/mysql8/config:/etc/mysql/conf.d \ -v /home/mysql8/logs:/logs \ -e MYSQL_ROOT_PAS…

YOLOv8模型剪枝笔记(DepGraph和Network Slimming网络瘦身)

文章目录 一、DepGraph剪枝&#xff08;1&#xff09;项目准备1&#xff09;剪枝基础知识2&#xff09;DepGraph剪枝论文解读12&#xff09;DepGraph剪枝论文解读23&#xff09;YOLO目标检测系列发展史4&#xff09;YOLO网络架构 &#xff08;2&#xff09;项目实战&#xff08…

App Builder技术选型指南:从AI编程到小程序容器,外卖App开发实战

在2025年快速迭代的技术生态中&#xff0c;开发者构建App的路径愈发多样化。本文以开发一个同城外卖App为例&#xff0c;对比当前主流的AI编程工具&#xff08;如Cursor、GitHub Copilot、Trae&#xff09;与小程序容器技术&#xff08;如FinClip&#xff09;的优劣势、难易度及…

TDengine 高可用——三副本

概述 TDengine 的三副本方案采用 RAFT 算法来实现数据的一致性&#xff0c;包括元数据和时序数据。一个虚拟节点组&#xff08;VGroup&#xff09;构成了一个 RAFT 组&#xff1b;VGroup 中的虚拟节点&#xff08;Vnode&#xff09;&#xff0c;便是该 RAFT 组的成员节点&…

el-table高度自适应、数据查询后高度展示错误问题

在很多场景中我们需要实现表格的高度自适应&#xff0c;即不同屏幕大小下需要使用不同的高度来设置表格&#xff0c;那么我们应该如何实现呢&#xff1f; 1.el-table实现高度自适应 通过以下代码可以实现表格根据屏幕进行自适应 设置表格的高度 <el-table ref"tableD…

Java接口设计:ECharts热力图的绘制

引言 热力图是一种强大的数据可视化工具&#xff0c;通过颜色的深浅变化来直观展示数据密度和分布情况。在现代Web应用中&#xff0c;ECharts作为一款流行的开源数据可视化库&#xff0c;提供了丰富的图表类型&#xff0c;其中热力图因其直观的视觉效果而被广泛使用。本教程将…

深入理解 MongoDB 的 _id 和 ObjectId:从原理到实践

在 MongoDB 的世界中&#xff0c;_id 字段和 ObjectId 是每个开发者都必须理解的核心概念。作为 MongoDB 文档的唯一标识符&#xff0c;它们不仅影响着数据库的设计&#xff0c;也直接关系到应用的性能和扩展性。本文将全面剖析 _id 和 ObjectId 的工作原理、实际应用场景以及最…

【notepad++如何设置成中文界面呢?】

“Notepad”是一款非常强大的文本编辑软件&#xff0c;将其界面设置成中文的方法如下&#xff1a; 一、工具&#xff0f;原料&#xff1a; 华为 Matebook 15、Windows 10、Notepad 8.4.6。 二 、具体步骤&#xff1a; 1、找到任意一个文本文件&#xff0c;比如 txt 格式的文…

当AI遇上科研:北大“科学导航”重塑学术探索全流程

在人工智能技术迅猛发展的当下&#xff0c;一场悄然发生的变革&#xff0c;正在改变我们“做科研”的方式。近日&#xff0c;北京大学科学智能研究院联合深势科技&#xff0c;正式上线一款面向科研人员的一体化AI平台——Science Navigator&#xff08;科学导航&#xff09;。这…

PHP学习笔记(八)

目录 返回值 return的使用 多值返回的替代方案 可变函数 内部&#xff08;内置&#xff09;函数 匿名函数 静态匿名函数 返回值 值通过可选参数的返回语句返回 return的使用 函数不能返回多个值&#xff0c;但可以通过返回一个数组来得到类似的效果 函数返回一个引用&am…

C#中WSDL文件引用问题

工作中碰到一个单点登录的需求&#xff0c;因为这个需求同事别的系统已经做过&#xff0c;我这边只需要把代码迁移过来即可&#xff0c;但是迁移过程中发现引用WSDL文件后&#xff0c;方法报错的问题&#xff0c;各种排查代码之后未解决&#xff0c;最终发现是WSDL文件引用的问…

养生新策:五维开启健康生活

一、饮食&#xff1a;天然食材&#xff0c;科学配比 以 “原型食物” 为主&#xff0c;减少加工食品摄入。早餐用鹰嘴豆泥涂抹全麦面包&#xff0c;搭配水煮蛋和一小把蓝莓&#xff0c;兼顾蛋白质与抗氧化物质&#xff1b;午餐选择藜麦饭&#xff0c;配上香煎鸡胸肉和蒜蓉空心…