JVM字符串常量池及String的intern方法详解?

关于上面的存储结构,我们已经在之前文章中提到,不再过多解释。下面如果我们再声明同样的字符串看看会有什么样的变化。

String wechat = “程序新视界”;
String wechat1 = “程序新视界”;

上述代码中声明wechat1时,会发现常量池中已经存在了对应的字符串,则不会再重新创建,只是把对应的引用返回给wechat1。对应结构图如下:

[

此时,如果直接用双等号比较wechat和wechat1肯定是相等的,因为它们的引用和字面值都是相同的。

上面是直接双引号赋值的情况,那么如果通过new的形式创建字符串对应的流程又是如何呢?前面文章已经讲到这分两种情况:常量池存在对应的值和不存在对应的值。

String wechat2 = new String(“程序新视界”);

如果存在对应的值,此时会先在堆中创建一个针对wechat2变量的对象引用,然后将这个对象引用指向字符串常量池中已经存在的常量。

此时直接使用双等号比较wechat和wechat2变量肯定是不相等的,而通过equals方法进行对比字面值则是相等的。

另外一种情况就是通过new创建时,字符串常量池中并不存在对应的常量。这种情况会现在字符串常量池中创建一个字符串常量,然后再在堆中创建一个字符串,持有常量池中对应字符串的引用。并把堆中对象的地址返回给wechat2。最终效果图依旧如上图。

在此时,如果不是直接new字符串赋值,而是通过+号操作,情况就有所不同。

String s1 = “程序”;
String wechat3 = new String(s1 + “新视界”);

上述代码s1会存入常量池,而wechat3的值则由于JVM编译时采用了StringBuilder进行加号的拼接,只会在堆中创建一个String对象,并不会在常量池中存储对应的字符串。

此时的情况已经涉及到我们面试题中创建字符串的情况了。那么,下面我们就通过intern方法进行池化操作,看看字符串常量池的具体变化。

还以上面的代码为例,此时wechat、wechat1、wechat2三个变量和wechat3直接用双等号比较肯定是不相等的。下面对wechat3进行intern池化处理。

String s1 = “程序”;
String wechat3 = new String(s1 + “新视界”);
wechat3 = wechat3.intern();

此时会发现wechat、wechat1两个变量与wechat3的值相等了。由于wechat和wechat1其实是一个,这里只以wechat和wechat3的比较为例来分析一下这个流程。

在没有调用intern方法之前内存的状态是下图(忽略掉s1部分)这样的:

看上图它们的值不相等也就不奇怪了。下面对wechat3进行池化处理,并把池化的结果赋值给wechat3,就是上面的代码。内存结构会发生如下变化:

此时,再判断对应的两个值,因为引用和字面值全部相同,因此便相等了。具体intern的判断规则我们上面已经知道,如果常量池中存在对应的值,则直接返回引用。

那还有另外一种情况,就是常量池中不存在对应的值会是如何处理的呢?先看如下代码:

String s2 = “关注”;
String wechat4 = new String(s2 + “公众号”);
wechat4 = wechat4.intern();

在调用intern之前的操作我们前面已经说过,会在堆中创建一个String对象,而常量池中并不会存储一份,与wechat3的图一样。

此时常量池中并未存在对应的字符串,此时调用intern方法之后,内存结构如下:

经intern方法之后,常量池中存了堆中对应字符串的引用。对照上面说的,JDK7及之后字符串常量池中可以存储引用了。

需要注意的是,当字符串常量池中并不存在对应字符串时,调用intern方法返回的地址为堆中的地址,对应图中的0x99。而wechat4本来地址指向的就是堆中的地址,因此不会发生变化。

此时如果再定义一个双引号赋值的wechat5,如下代码:

String s2 = “关注”;
String wechat4 = new String(s2 + “公众号”);
wechat4 = wechat4.intern();

String wechat5 = “关注公众号”;
System.out.println(wechat4 == wechat5);

变量wechat5初始化时发现字符串常量池中已经存在了一个引用,那么wechat5会直接指向这个引用,也就是wechat5和wechat4一样,都指向内存中的String对象。

小结

上面这个演示实例时需要注意的重点是intern方法返回的引用地址。如果字符串常量池中已经存在对应的字符串时,此时返回的是字符串常量的地址【常量池中存储的是字符串】,如果字符串常量池中不存在对应的字符串,此时会把堆中的引用放在常量池对应的位置【常量池中存储的是堆中字符串的引用】,此时intern返回的是堆中字符串对应的引用。

搞清楚了上面的返回逻辑再看最初的代码:

String s1 = new String(“he”) + new String(“llo”);
String s2 = new String(“h”) + new String(“ello”);

String s3 = s1.intern();
String s4 = s2.intern();
System.out.println(s1 == s3);
System.out.println(s1 == s4);

其中s1为堆中字符串“hello”的地址;s2为堆中另外一个“hello”字符串的地址。当s1.intern(),常量池中存储了s1的地址,此时s1.intern()返回的也是s1的地址,因此s1=s3,都是同一个地址嘛。

然后执行s2.intern(),此时常量池中已经有hello字符串,类型为引用且指向s1的地址,执行之后返回的便是s1的地址,赋值给s4,因此s1和s4也指向同一个地址,因此相等。

通过上面的更深层次的分析,想必大家对字符串常量、字符串常量池以及intern方法有了更加深刻的理解。相关的面试题如果按照这个思路分析,基本上都可以进行准确解答了。
##最后
大家看完有什么不懂的可以在下方留言讨论.
谢谢你的观看。
觉得文章对你有帮助的话记得关注我点个赞支持一下!

作者:secbro2
链接:https://juejin.im/post/6868054744015831054

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!](https://bbs.youkuaiyun.com/topics/618164986)

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

<think>好的,我现在需要回答用户关于Java字符串常量池的问题。首先,我得回忆一下相关知识,并参考提供的引用内容。 根据引用[1],字符串常量池存储在Java堆内存中,用于管理字符串对象。用户可能想知道它的基本概念和工作原理。引用[4]提到,使用双引号创建字符串时,会检查常量池是否存在该字符串,存在则复用,否则新建。而new操作符会在堆中创建新对象,可能不在常量池中。 接下来,我需要解释字符串常量池的作用,比如节省内存、提高性能。引用[4]指出常量池实现字符串共享,减少重复创建。这里要注意不同JDK版本的变化,如Java7之后常量池移到堆中,而之前是在方法区,但用户问题可能不需要深入版本差异,除非特别提问。 然后,结合示例说明,比如直接赋值和使用new的区别。引用[2]中的例子显示,s1和s2指向同一常量池对象,所以==返回true。而用new创建的会生成新对象,==比较为false,需用equals比较内容。 最后,可能需要提到intern方法,将字符串加入常量池。不过引用中没有详细说明,但根据知识,intern方法可以主动将字符串放入池中,便于后续复用。 总结回答结构:定义、作用、工作流程、示例说明、注意事项。需要确保用中文,行内公式正确,独立公式单独成段。然后生成相关问题,如常量池位置变化、intern方法、性能影响等。</think>### Java字符串常量池详解 #### 1. 定义与作用 Java字符串常量池String Pool)是**存储在堆内存中的特殊存储区域**,用于**缓存字符串字面量**,实现字符串对象的复用[^1]。例如: ```java String s1 = "hello"; // 常量池中创建"hello" String s2 = "hello"; // 复用常量池中的对象 System.out.println(s1 == s2); // true(地址相同)[^2] ``` #### 2. 工作流程 1. **直接赋值**: 使用双引号创建字符串时,JVM会优先检查常量池是否存在该字符串: - 存在 → 直接返回引用 - 不存在 → 在池中创建新对象并返回引用 2. **new关键字**: 强制在堆中创建新对象(可能同时向常量池添加对象): ```java String s3 = new String("hello"); // 堆中新建对象,与常量池对象无关 System.out.println(s1 == s3); // false(地址不同)[^4] ``` #### 3. 内存结构示意图 $$ \begin{array}{|c|c|} \hline \text{堆内存(Heap)} & \text{字符串常量池String Pool)} \\ \hline \downarrow & \downarrow \\ \text{new String("A")} & \text{"A"} \\ \text{new String("B")} & \text{"B"} \\ \hline \end{array} $$ #### 4. 关键特性 - **自动去重**:相同字面量指向同一内存地址 - **手动干预**:可通过`intern()`方法将堆中字符串添加到常量池 - **版本差异**:JDK7前常量池方法区(PermGen),之后移至堆内存[^3] #### 5. 性能影响 - **优势**:减少重复对象创建,降低内存消耗 - **风险**:滥用`intern()`方法可能导致内存溢出(如处理大量唯一字符串时)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值