【Java】String字符串在JVM中的存储及其内存地址问题

概要

String 的内存地址问题是Java面试中常被问到的一个点,比如直接复制的String 和 new 出来的 String 有什么区别?字符串拼接过程中地址是如何变化的?等等。
要想理清这些地址问题,我们首先应当知道 String 在JVM中是如何存储的。

1. String 对象在JVM中的存储

先给出定义:
字符串存放在方法区的常量池(Constant Pool)中,常量池是什么呢?
常量池在
编译期间
就会被生成,用于存放编译器生成的各种字面量和符号引用。比如int a = 8;String a = “abc”; 这种里面的8和"abc"在编译阶段就会被放入常量池。
当然常量池在运行期间也可以被拓展,将新的常量放入池中,用的比较多的就是String 的 intern() 方法,后面会详细描述。

再来看这样一段简单的代码

	String str = "aa";
	String str1 = "aa";
	String str2 = new String("aa");
	String str3 = new String("aa");
	System.out.println(str == str1);//true
	System.out.println(str2 == str3);//false
	System.out.println(str == str2);//false

为什么会是这样的结果呢?
按照程序的执行顺序,首先,“aa”作为一个字面量,也就是常量,会在编译期间被加入常量池,然后JVM将其在常量池中的地址赋给str;到了str1这里,JVM先在常量池中查找有没有“aa”这个常量,由于给str赋值的时候已经在常量池里创建过“aa”了,所以JVM直接返回这个地址给str1。因此str和str1的地址是一样的,结果为true。

来到str2和str3,这两个是new出来的String,是对象,对象存放在哪里呢?存放在堆中,所以本质上str2和str3存放的是这两个对象在堆中的地址。new了两次,他俩是不同的对象,所以str2和str3地址不相同,返回false。

现在再来看最后一个输出,str == str2?这俩一个存的是常量池中的地址,一个存的是堆中的地址,怎么可能相等嘛,返回false。

2.关于字符串拼接

来看这样一段代码

	String a = "Hello2";
	String b = "Hello";
	final String c = "Hello";
	
	String d = b + "2";
	String e = c + "2";
	String f = "Hello" + "2";
	
	System.out.println(a==d);//false
	System.out.println(a==e);//true
	System.out.println(a==f);//true

首先a、b、c都是字面量直接赋值,所以现在常量池中有 “Hello2” 和 "Hello"两个字符串。根据上面的结果我们可以得到如下两个结论:

(1)字符串相加的时候,都是静态字符串相加的结果会添加到常量池,如果常量池中有这个结果则直接返回其引用;如果没有,则创建该字符串再返回引用。因为现在常量池已经有 “Hello2” 了,所以e和f的值其实都是常量池中 “Hello2” 的引用,a、e、f都是相等的。

(2)字符串相加的时候,如果其中含有变量,如d中的b,则不会进入常量池中。在IDEA中DeBug,我们强制进入 String d = b + “2”; 这条语句,会发现程序底层其实是先创建了一个StringBuffer,然后调用append()方法,把b和"2"加入该StringBuffer,然后调用toString()返回拼接后的字符串,而最终的toString()源码长这个样子:

@Override
public String toString() {
    // Create a copy, don't share the array
    return new String(value, 0, count);
}

看到了吗!它返回了一个new的String!这个String的地址当然不会任何一个现有的对象相同了。
关于字符串拼接,分清楚这两种情况即可。

3. 关于intern()方法

前面我们提到过,new 出来的String不直接存放在常量池中,而intern()方法的作用就是把这个字符串加入到常量池中,然后返回这个字符串在常量池中的地址。

String str1 = "a";
String str2 = "b";
String str3 = "ab";
String str4 = str1 + str2;
String str5 = new String("ab");
 
System.out.println(str5 == str3);//false
System.out.println(str5.intern() == str3);//true
System.out.println(str5.intern() == str4);//false
System.out.println(str5.intern() == str4.intern());//true

调用str5.intern()时,JVM在常量池中查询有没有"ab"这个字符串,因为在给str3赋值时已经创建过,所以直接返回其地址。str4由两个变量相加得到,所以也相当于是new出来的。

还有一个需要注意的点就是:如果只是调用str5.intern(),那str5本身并不会改变,还是存放的堆里的地址,想让str5存放常量池中的地址需要把str5.intern()的返回值再赋给str5。可以做如下测试:

String str3 = "ab";
String str5 = new String("ab");
str5.intern();
System.out.println(str5 == str3);//false
str5 = str5.intern();
System.out.println(str5 == str3);//true
  • 15
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
StringJVM存储方式是通过一个char数组来存储字符串的字符数据。在JDK8及以前的版本String内部定义了一个final char\[\] value来存储字符串数据。而在JDK9String存储结构发生了变化,改为了使用byte\[\]来存储字符串数据。\[2\] String对象在JVM存储位置有两种情况: 1. 字符串常量池:在JDK,双引号括起来的字符串常量,例如"abc"、"def",都是直接存储在方法区的字符串常量池。这是因为字符串在实际开发使用非常频繁,为了提高执行效率,将字符串放在字符串常量池。 2. 堆内存:使用new关键字创建的字符串对象会在堆内存开辟空间。例如在示例代码,使用new关键字创建的字符串对象"c",会在堆内存开辟空间存储字符串数据。\[3\] 需要注意的是,字符串是不可变的,即一旦创建就不能修改。这是因为String类被声明为final,不可被继承,并且String对象在JVM是不可变的。这意味着一旦字符串对象被创建,它的值就不能被改变。\[3\] #### 引用[.reference_title] - *1* [【JavaString字符串JVM存储及其内存地址问题](https://blog.csdn.net/weixin_43390123/article/details/124376835)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [JVM上篇_13_StringTable_尚硅谷](https://blog.csdn.net/weixin_43811294/article/details/125462300)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Java基础String字符串存储原理](https://blog.csdn.net/qq_46096136/article/details/126533585)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值