news 2026/6/22 7:47:17

JVM篇2-StringTable、直接内存、垃圾回收

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JVM篇2-StringTable、直接内存、垃圾回收

先来说一个常考的面试题。

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

运行时常量池像是类的"档案袋",保存了类的各种常量信息;字符串常量池则是一个专门的"字符串缓存区",用来优化字符串的存储和使用。当类加载时,运行时常量池中的字符串字面量以及编译期可确定的常量表达式结果(如"a"+"b"即"ab"会存入字符串常量池)会被处理,最终在字符串常量池中创建对应的字符串对象。
JDK 1.8+ 内存结构:
┌─────────────────────┐
│ 堆 │
│ ┌───────────────┐ │
│ │字符串常量池 │ │ ← 字符串对象/引用
│ └───────────────┘ │
│ ┌───────────────┐ │
│ │普通对象实例 │ │
│ └───────────────┘ │
└─────────────────────┘
┌─────────────────────┐
│ 元空间 │
│ ┌───────────────┐ │
│ │运行时常量池 │ │ ← 类元信息、符号引用
│ └───────────────┘ │
│ ┌───────────────┐ │
│ │类信息 │ │
│ └───────────────┘ │
└─────────────────────┘

(1)位置

JDK 1.6及之前:两者都在方法区(永久代)
JDK 1.7:字符串常量池移到堆中,运行时常量池在永久代(方法区)
JDK 1.8+:字符串常量池仍在堆中,运行时常量池在元空间(方法区)

(2)存储内容不同

运行时常量池包含:类的全限定名、字段名和描述符、方法名和描述符(描述符是指在字节码中完整描述类型信息)、各种字面量(包括字符串字面量)、符号引用
字符串常量池包含:字符串对象或引用(JDK 1.7+)、字符串对象实例(JDK 1.6)。

(3)创建时机不同

运行时常量池创建时机 :类加载时创建,当类被加载到JVM时,类文件中的常量池信息被加载到运行时常量池。
字符串常量池创建时机:动态创建和维护。情况1,类加载时可能创建(如果类中有字符串字面量);情况2,运行时动态创建intern()。
例如:

// 情况1:类加载时可能创建(如果类中有字符串字面量)Strings1="hello";// 类加载时可能将"hello"放入字符串常量池// 情况2:运行时动态创建Strings2=newString("world").intern();// 运行时动态入池Strings3="hel"+"lo";// 编译期优化,可能使用常量池已有对象

注意,上述代码的"hello"字面量、"world"字面量都会在类加载时放入字符串常量池,且s1,s2,s3指向字符串常量池地址。
而下面的代码:

Strings4=newString("apple");

"apple"字面量会入池,但new出来的对象在堆中,s4指向堆中地址。

(4)作用不同

运行时常量池:1. 存储类的元信息,支持动态链接;2. 存放符号引用,在解析阶段转为直接引用
字符串常量池: 1. 提高字符串重用,节省内存; 2. 提升字符串操作性能。
例如:

publicclassPurposeDifference{publicstaticvoidmain(String[]args){// ===== 运行时常量池的作用 =====// 1. 存储类的元信息,支持动态链接Class<?>clazz=String.class;// 类引用从运行时常量池获取// 2. 存放符号引用,在解析阶段转为直接引用Objectobj=newObject();// Object类的符号引用在运行时常量池// ===== 字符串常量池的作用 =====// 1. 提高字符串重用,节省内存Stringstr1="java";Stringstr2="java";// 复用常量池中的对象System.out.println(str1==str2);// true// 2. 提升字符串操作性能// 避免重复创建相同内容的字符串}}

总结:

一、StringTable(字符串常量池)

先看一道面试题:

1.1 字符串拼接

先来看一下其他的例子:


常量池中的信息,都会被加载到运行时常量池中,这之前a,b,ab都是常量池中的符号,还没有变为java中的字符串对象。
当执行到虚拟机指令的ldc命令时,

会把a符号变为字符串对象"a",会准备一块StringTable空间(StringTable是一个哈希表,不能扩容),会将key到StringTable中去找,没有则存入StringTable串池,b和ab符号原理相同。
接下来增加拼接字符串的代码:


可以看到底层使用StringBuilder进行拼接然后new String()了一个新的字符串对象(new String()得到的对象存在堆中)。

字符串变量的拼接原理是StringBuilder()。

1.2 编译期优化


上图中蓝色部分的ldc指令可以看到直接加载了"ab"字符串,和虚拟机指令“6:ldc #4”结果一致,当执行ldc将符号加载入运行时常量池中的StringTable时,由于StringTable中已经有该字符串了,所以不会存一个新的对象。

上述结果是javac在编译期间的优化,结果已在编译期确定为ab,而变量s1+s2的拼接方式,s1和s2是变量,可能发生变化,所以编译期间不能确定,只有在运行期间才能确定。
字符串常量的拼接的原理是编译器优化。

1.3 字符串延迟加载



通过上图中的debug过程,可以发现,在执行过程中内存中的字符串个数逐渐增加(所以在编译时并不会把所有字符串加载在运行时常量池中,而是运行时用到了再加载,即常量池中的字符串仅是符号,第一次用到时才会变为对象),且已经加载过的字符串不会重复加载。

1.4 intern()

1.4.1 定义

intern()方法是主动将串池(字符串常量池)中还没有的字符串对象放入串池。
不同版本的intern()的底层原理:

说明:JDK1.6的intern()是深拷贝,复制一个字符串对象放入到串池中,串池中的字符串和堆中的字符串地址不相同。JDK1.7+的intern()是浅拷贝,将堆中new String()对象的引用地址拷贝到串池中,且后面的字符串都指向该引用地址。

1.4.2 JDK1.6和JDK1.7+对比

示例一

Strings=newString("a")+newString("b");Strings2=s.intern();System.out.println(s2=="ab");System.out.println(s=="ab");

对于上述代码,JDK1.6和JDK1.7+的结果不同。

(1)JDK1.6的执行结果
truefalse

代码分析:

Strings=newString("a")+newString("b");// 1. 创建了两个字符串常量"a"和"b"(如果常量池没有的话)// 2. 创建两个String对象(堆中)// 3. StringBuilder拼接,生成"ab"(堆中),注意此时"ab"不在常量池// s指向堆中的"ab"对象Strings2=s.intern();// intern():如果常量池没有"ab",则会在常量池新建一个"ab"字符串对象//s2 指向常量池中的新对象,s 仍然指向堆中的原对象System.out.println(s2=="ab");// trueSystem.out.println(s=="ab");// false
(2)JDK1.7+的执行结果
truetrue

原因:s.intern() 发现常量池没有"ab",不会在常量池新建对象,而是将堆中"ab"对象的引用记录到常量池,s2 和 “ab” 字面量都指向堆中的同一个对象(即 s)。

示例二

Stringx="ab";Strings=newString("a")+newString("b");Strings2=s.intern();System.out.println(s2==x);System.out.println(s==x);

对于上述例子,JDK1.6和JDK1.7+的结果相同。

truefalse

执行分析
第一步:String x = “ab”;
先在常量池创建"ab"字符串(如果还没有的话),x 直接指向常量池中的"ab"。
第二步:String s = new String(“a”) + new String(“b”);
创建堆中的"ab"对象(通过StringBuilder拼接),s 指向堆中的"ab"对象。此时常量池已经有"ab"了(第一

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/20 11:40:18

SAM预测器SamPredictor详解

SAM预测器详解 文章目录 SAM预测器详解 1.1 SamPredictor详解 1.1.1 SamPredictor的使用 1.1.2 SamPredictor在SAM架构中的位置 1.1.3 SamPredictor代码详解 1.2 SamPredictor类的初始化 1.3 set_image方法:图像编码和特征缓存 1.3.1 图像预处理流程 1.3.2 图像编码和缓存 1.4…

作者头像 李华
网站建设 2026/6/22 7:46:19

URLFinder:如何快速发现网页中的隐藏资源和敏感信息?

URLFinder&#xff1a;如何快速发现网页中的隐藏资源和敏感信息&#xff1f; 【免费下载链接】URLFinder 一款快速、全面、易用的页面信息提取工具&#xff0c;可快速发现和提取页面中的JS、URL和敏感信息。 项目地址: https://gitcode.com/gh_mirrors/ur/URLFinder 你是…

作者头像 李华
网站建设 2026/6/22 7:46:18

YimMenu终极指南:GTA5游戏助手与安全防护完全手册

YimMenu终极指南&#xff1a;GTA5游戏助手与安全防护完全手册 【免费下载链接】YimMenu YimMenu, a GTA V menu protecting against a wide ranges of the public crashes and improving the overall experience. 项目地址: https://gitcode.com/GitHub_Trending/yi/YimMenu …

作者头像 李华
网站建设 2026/5/20 11:37:20

Docker容器化高可用架构部署方案(十二)

11-MySQL-MGR初始化 本文档详细介绍MySQL MGR&#xff08;Group Replication&#xff09;集群的初始化步骤。 初始化前提 三个MySQL容器已正常运行 MySQL容器healthcheck通过 网络连通性正常 初始化步骤 步骤1&#xff1a;等待MySQL容器就绪 # 查看MySQL容器状态 docke…

作者头像 李华
网站建设 2026/6/17 14:45:03

md怎么转txt?全网热门转换方法+在线工具对比(2026实测)

平时工作中积累了不少Markdown笔记&#xff0c;想把它们转成纯文本格式方便阅读和分享&#xff0c;却发现转换方法多到眼花缭乱&#xff1f;本文将为你详细对比2026年主流的md转txt方法&#xff0c;从最简便的改后缀方案到专业级工具转换&#xff0c;帮你快速找到最适合自己的转…

作者头像 李华