最近在整理面试题的时候,发现了这样一个问题…… 小问号,你是否也会有这样的朋友?
- JVM内存总览
Java中的变量(包括对象变量)和基本类型的值存放于栈内存。 栈内存的特点之一就是共享数据,接下来举一个String的例子,来体会栈内存中的对象变量共享字符串常量池。
举个栗子
先上代码:
public class StringDemo {
public static void main(String[] args) {
String str1= "abc"; //1
String str2= "abc"; //2
String str3=new String("abc");//3
String str4=new String("abc"); //4
System.out.println(str1==str2); //true
System.out.println(str3 == str4); //false
}
}
- 解释1:执行语句1时
String str1= "abc",JVM首先会检查字符串常量池中是否存在该字符串对象。
- 如果已经存在,那么就不会再创建了,直接返回该字符串常量池中的内存地址。
- 如果不存在,那么就会在字符串常量池中创建该字符串对象,然后返回该字符串常量池中的内存地址。
所以栈内存中的str1和str2的内存地址都是指向“abc”在字符串常量池中的位置。
- 解释2:执行语句3
String str3=new String("abc")时,JVM会首先检查字符串常量池中是否已经存在字符串对象。
- 如果已经存在,则不会在字符串常量池中创建;如果不存在,则会在字符串常量池中创建“abc”字符串对象。
- 然后到堆内存中创建一份字符串对象,把字符串常量池中的“abc”字符串内容拷贝到堆内存中的字符串对象中。最后返回堆内存中该字符串的内存地址。
即栈内存中的str3和str4都指向堆内存中的对象。且str3和str4并未指向同一个对象。
关于字符串常量池到底在哪的问题?
引用—帅地
- 在JDK1.8中,使用元空间代替永久代来实现方法区,但是方法区并没有改变。 所谓”Your father will always be your father”,变动的只是方法区中内容的物理存放位置。 正如上面所说,类型信息(元数据信息)等其他信息被移动到了元空间中;但是运行时常量池和字符串常量池被移动到了堆中。 但是不论它们物理上如何存放,逻辑上还是属于方法区的。
JDK1.8中字符串常量池和运行时常量池逻辑上属于方法区,但是实际存放在堆内存中,因此既可以说两者存放在堆中,也可以说两则存在于方法区中,这就是造成误解的地方。
题目——String相加的问题
引用——smartleon
- 对于字符串常量的相加,在编译时直接将字符串合并,而不是等到运行时再合并。也就是说,String a = "tao" + "bao" ;和String a = "taobao" ;编译出的字节码是一样的。所以等到运行时,由于栈内存是数据共享原则,a和MESSAGE指向的是同一个字符串。 而对于后面的(b+c)又是什么情况呢?
- Java对String的相加是通过StringBuffer实现的,先构造一个StringBuffer里面存放”tao”,然后调用append()方法追加”bao”,然后将值为”taobao”的StringBuffer转化成String对象。StringBuffer对象在堆内存中,那转换成的String对象理所应当的也是在堆内存中。
再把变量b和c的定义改一下:
final Stringb = "tao" ;
final String c = "bao" ;
System. out .println( (b+c)== MESSAGE );
现在b和c不可能再次赋值了,所以编译器将b+c编译成了”taobao”。因此,这时的结果是true。 在字符串相加中,只要有一个是非final类型的变量,编译器就不会优化,因为这样的变量可能发生改变,所以编译器不可能将这样的变量替换成常量。
如果觉得写的不错的话,欢迎一键三连~