展开菜单
首页 精品内容 本月促销 装机必备 Windows macOS软件 IOS软件 Android AI PDF教程 专题
全部分类

当前位置:

首页 > 编程开发 > Java中String在内存里到底是怎么存的

Java中String在内存里到底是怎么存的

字符串字面量存储在字符串常量池中,JDK7后常量池移至堆。newString("abc")会在堆创建实例,若常量池无对应字面量则额外创建。String具有不可变性、共享性和动态性,堆实例与常量池共享底层字符数组。

说在前面:

关于JVM的五个核心数据区域,前面已经聊了三块内容。按照计划,接下来应该讲垃圾回收了。但学到这里时,发现有一个知识点绕不过去——String的存储机制。原因很简单:要搞懂GC,先得弄明白对象在堆里是怎么分配的。而String恰好是个典型例子——它既有对象实例在堆里,又有字面量待在常量池中,还牵扯到“两个对象还是一个新对象”这类经典问题。所以,趁这个机会插一篇文章,专门把String在内存里的位置彻底捋清楚。

Ja va中String在内存里到底是怎么存的

先问你一个问题

String s1 = "abc";
String s2 = new String("abc");

这两行代码,s1和s2指向的,是同一个对象吗?

不少人一开始会直觉认为:不都是“abc”吗,肯定一样啊。但答案是:不一样。s1指向的是常量池里的“abc”,而s2指向的是堆里new出来的那个实例。

这个结论,可以说是理解String内存模型的认知转折点。下面我们就围绕这件事展开。

字符串字面量存在哪里

先说结论:字符串字面量,存在字符串常量池里。

所谓字面量,就是你在代码里直接用双引号包起来的那些字符串,比如:

"hello"
"abc"
"JVM 真的很难学"

这些字符串在编译阶段,就会被编译器收进class文件的常量池。等到类加载完成之后,它们会被放进字符串常量池中。

常量池的位置变过

一个很有意思的细节是——字符串常量池的位置,在JDK 7的时候发生过一次迁移:

JDK 版本 字符串常量池的位置
JDK 6 及之前 方法区(永久代)
JDK 7 起 堆中

为什么搬?因为永久代(PermGen)的空间是固定且有限的。字符串常量池里的字符串一多,就容易把永久代撑爆,抛OutOfMemoryError: PermGen space。搬到堆里之后,堆空间可以动态扩展,GC也能更从容地管理它。

new String("abc")到底干了什么

这个操作,可以说是最值得搞明白的细节了,也是面试题里的常客。

看这行代码:

String s = new String("abc");

很多人以为它就创建了一个对象。实际上最多创建了两个对象

拆解一下步骤:

第一步:new指令在堆里创建一个String实例

new关键字就是在堆上分配内存,创建一个String对象。这个对象此刻还是个“空壳”——它的值还没确定。

第二步:JVM拿着字面量去常量池里找

因为构造方法里传了"abc"这个字符串字面量,JVM会去字符串常量池里找一找,有没有"abc"这个字符串。

这时候有两种情况:

情况一:常量池里之前没有“abc”

那JVM就会在常量池里创建一个"abc"对象。这时候就有两个对象了:一个是常量池里的"abc",一个是堆里的new String()实例。后者会引用前者的值。

情况二:常量池里已经有“abc”了

那就不创建新的了,直接复用已有的。这时候只创建了一个对象——就是堆里那个new String()实例。

第三步:引用s存在当前方法的栈帧里

不管创建了几个对象,最终s这个引用存在当前方法的栈帧里,指向堆里的那个String实例,而不是直接指向常量池里的“abc”

一张图看清楚

栈(Stack)               堆(Heap)
┌──────────┐            ┌──────────────────────────┐
│ s (引用)  │ ────────→ │  new String("abc") 实例   │
└──────────┘            │                         │
                        │  字符串常量池(堆中)     │
                        │  ┌───────────────────┐  │
                        │  │    "abc" 字面量    │  │
                        │  └───────────────────┘  │
                        └──────────────────────────┘

注意箭头的方向:snew String("abc")实例 → 常量池中的"abc"(作为内部char[]引用)。

怎么验证?

写一段代码验证一下,结果就很清晰:

String s1 = "abc";
String s2 = new String("abc");

System.out.println(s1 == s2);       // false:s1 指向常量池,s2 指向堆
System.out.println(s1.equals(s2));  // true:内容是一样的

s1 == s2是false,说明它们不是同一个对象——一个在常量池里,一个在堆里。而s1.equals(s2)是true,因为equals()比较的是字符串的内容,不是引用地址。

字符串常量池的四个关键特性

梳理下来,字符串常量池有四个关键特性值得记住:

1. 不可变性

String对象一旦创建,它的值就不能改了。你所做的任何“修改”操作(比如concat()replace()substring())都是创建了一个新对象,原来的字符串纹丝不动。

很多人不理解为什么String要设计成不可变的。其实,不可变性是常量池能工作的前提——如果字符串可变,两个引用共享同一个“abc”,其中一个改了,另一个就跟着乱套了。

2. 共享性

相同的字符串字面量在常量池里只存一份。不管你在代码里写了多少次"hello",常量池里只会有一个"hello"对象,所有引用都指向它。

这就是为什么前面那个例子可以用==判断:

String x = "abc";
String y = "abc";
System.out.println(x == y);  // true:指向同一个常量池对象

3. 动态性

常量池不是编译完就定死了,程序跑起来之后也可以往里面加内容。

最典型的例子是String.intern()方法:

String s = new String("abc").intern();

intern()的作用是:去常量池里找有没有相同内容的字符串,如果有就返回常量池里的那个引用;如果没有,就把当前字符串的内容放到常量池里。可以说,intern()方法的本质是:手动把堆里的字符串“注册”到常量池里。

4. 位置挪到了堆里

JDK 7之前常量池在永久代,JDK 7之后挪到了堆里。这样做的好处很明显:

  • 堆空间可以动态扩展,不用担心PermGen太小
  • 堆的GC机制更强,常量池里的无用字符串可以被回收(虽然概率不高,但至少有了这个能力)

一个让人纠结了好久的细节

学这篇的时候有一个细节让人纠结了很久:new String("abc")创建的两个对象,在内存里到底是什么关系?

画一个更详细的图,帮助理解:

栈                    堆
┌─────────┐         ┌────────────────────────────┐
│ s        │ ──────→│ new String (value = char[]) │
│ (引用)   │         │  ┌──────────────────────┐  │
└─────────┘         │  │ char[] 引用 ────────→ │  │
                    │  └──────────────────────┘  │
                    │                            │
                    │ 字符串常量池               │
                    │  ┌──────────────────────┐  │
                    │  │ "abc" → char[]对象    │  │
                    │  └──────────────────────┘  │
                    └────────────────────────────┘

new出来的String对象内部有一个value字段(char[]类型),它引用的就是常量池里那个"abc"的底层char数组。

所以整个链路是:s(栈引用)→ new String实例(堆对象)→ 共享的char[]数组(常量池的底层数据)。

也就是说,常量池里的"abc"和new出来的String,共享了底层的字符数据。常量池存了一份char[],堆里的对象拿着一个引用指向它,而不会再重复存一份内容。

明白了这个机制,再回头看“两个对象”这个说法,才能真正理解是哪两个对象,以及它们之间的关系。

最后串一下:和前面几篇的关系

学到这里会发现,这一篇其实是把前面几篇的知识点串起来了:

  • ——方法里的String引用存在栈帧里
  • ——new String()的实例存在堆里
  • 方法区/元空间——类的字节码信息、class文件常量池在这个区域
  • 字符串常量池——JDK 7+在堆里,存字符串字面量

一个简单的String s = new String("abc"),牵扯到了JVM三大内存区域。学完前三篇再回头看这个,确实有一种“哦,原来如此”的感觉。

本文内容来源于互联网,如有侵权请联系删除。
作者最新文章
编程开发
相关文章 更多
精品专题 更多
装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

IOS软件

正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

AI

正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

PDF教程

PDF教程适合刚接触PDF文件的用户,本文整理PDF打开、编辑、转换、合并、压缩等常见基础操作。通过这些教程可以快速了解PDF文件怎么处理,解决办公、学习和资料科整理中的常见问题。使用极轻PDF可在线完成多种PDF操作,适合新手快速上手。

Mac软件 更多
灵活计算器
灵活计算器

灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

赤友清理大师
赤友清理大师

赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

极度公式
极度公式

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

图几
图几

图几是一款适用于 macOS 的截图、标注与美化工具,支持离线操作保障隐私。界面整理和高频系统操作被放到一起考虑,桌面或窗口内容一多时,管理起来会更省心。

密码键盘
密码键盘

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

思源笔记
思源笔记

思源笔记是一款本地笔记软件,提供所见即所得的编辑方式,为长文写作带来顺滑的体验。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

Office 365 简体中文
Office 365 简体中文

一款文字处理软件,一种订阅式的跨平台办公软件,基于云平台提供多种服务,通过将 Excel 和 Outlook 等应用与 OneDrive 和 Microsoft Teams 等强大的云服务相结合,Office 365 可让任何人使用任何设备随时随地创建和共享内容。

WALTR PRO
WALTR PRO

WALTR是一款电脑至iOS文件传输转换工具,操作简单,快速实现文件识别与传送。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

CodeExpander
CodeExpander

CodeExpander 是一款快捷短语输入增强工具,通过键入缩写自动展开为自定义文段,提升工作效率。任务管理和过程控制会更完整,持续下载、批量同步或需要稳定传输流程的场景会更适合它。

Mountain Duck
Mountain Duck

Mountain Duck 是一款能将多个网盘挂载到本地的工具,像本地磁盘一样使用网盘。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

Menuist
Menuist

Menuist 是一款面向 macOS 的 Finder 右键菜单增强工具,主要用来补充新建文件、快捷导航等常用操作,让日常文件管理和访问路径时更高效、更顺手。

Mole
Mole

Mole 是一款专为 Mac 设计的深度清理优化工具,涵盖缓存清理、应用管理及实时状态监控等功能。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

WINDOWS 更多
Windows 10
Windows 10

Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

极度公式
极度公式

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

密码键盘
密码键盘

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

思源笔记
思源笔记

思源笔记是一款本地笔记软件,提供所见即所得的编辑方式,为长文写作带来顺滑的体验。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

傲梅轻松备份
傲梅轻松备份

傲梅轻松备份是一款专业易用的数据备份软件,为重要数据提供安全保障。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

Office 365 简体中文
Office 365 简体中文

一款文字处理软件,一种订阅式的跨平台办公软件,基于云平台提供多种服务,通过将 Excel 和 Outlook 等应用与 OneDrive 和 Microsoft Teams 等强大的云服务相结合,Office 365 可让任何人使用任何设备随时随地创建和共享内容。

Wise Folder Hider Pro
Wise Folder Hider Pro

Wise Folder Hider Pro 是一款专业级文件和文件夹隐藏加密软件,为私密数据添加多重保护。高频操作更强调就近处理,浏览、整理和跨目录移动文件时,来回切换和重复点击都会少很多。

WALTR PRO
WALTR PRO

WALTR是一款电脑至iOS文件传输转换工具,操作简单,快速实现文件识别与传送。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

CodeExpander
CodeExpander

CodeExpander 是一款快捷短语输入增强工具,通过键入缩写自动展开为自定义文段,提升工作效率。任务管理和过程控制会更完整,持续下载、批量同步或需要稳定传输流程的场景会更适合它。

PinStack
PinStack

PinStack是一款轻量级的Windows平台剪贴板管理工具,优化您的剪贴板使用体验。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

Mountain Duck
Mountain Duck

Mountain Duck 是一款能将多个网盘挂载到本地的工具,像本地磁盘一样使用网盘。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

Seer
Seer

Seer是一款在Win平台下的空格键功能增强效率工具,只需轻敲空格键,就能预览几乎任何格式的文件。它更适合把零散的小功能集中起来使用,处理高频琐碎任务时会更省事。