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

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

概要

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

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/728183.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-26
下一篇 2022-04-26

发表评论

登录后才能评论

评论列表(0条)

保存