从字节码看String
从一道很古老的面试题说起
public class TestString {
public static void main(String[] args) throws InterruptedException {
String a = new String("abc");
String b = new String("abc");
}
}
一般都会这么问,在执行String a = new String("abc");String b = new String("abc");
这两句代码的时候,会创建几个对象,按照网上流传的方法分析的话,大概是3
个,先将一个"abc"
字面量(String litera
)放入常量池中,然后创建两个String
对象,指向这个字符串常量,我们知道,在字节码中,能够数组的指令有newarray
,anewarray
,multianewarray
,但创建一个类实例的指令只有new
.
我们拿出这段代码的字节码形态
只有两个new
指令,根据对称性很容易看出来,0-9是第一行代码,10-19是第二行代码,而关于我们所说的字符串字面量却并没有在这里被创建,而只是从常量池中通过ldc
加载过来,也就是说,如果单纯的只说这两行代码的执行阶段创建了几个对象,应该说只创建了两个,但是如果说涉及了几个对象,那当然还是三个.
编译期优化
我们再来看一段代码
String a = "a";
String ab = a+"b";
我们知道,若是"a"+"b"
那么在编译的时候就会自动优化为"ab"
,但如果是a+"b"
则不会如此,为什么呢?笔者认为,之所以前者可以被优化,是因为在编译期是已知的,但换做引用就说不准了,不运行,就不会知道最终所引用的对象是什么样子的,因此不能如此优化.那这里可以说创建了几个对象呢? 我们说,有几个new,就创建了几个对象,这里创建了一个StringBuilder
,以及一个String
对象,但这里只有一个new
,另一个new
在那个toString
方法中.
与之作为对比将a声明为final
final String a = "a";
String ab = a+"b";
则可以在编译期完成优化,字节码如下
StringBuilder创建
再让我们回到前一份字节码,可以验证a+"b"
的这个连接操作实质上确实如网上所说是通过一个StringBuilder
实例来完成的.
我们再通过字节码来验证应该自己手动创建StringBuilder
这句话的正确性
String c = "";
for(int i =1;i<4;i++){
c+=i;
}
图中红色区域即为循环体,可以看到,隐式使用StringBuilder
是会在每次迭代时都创建一个新的实例,因此会导致效率降低.
但值得注意的是,在java11
中,就不是采用的StringBuilder
了,而是StringConcatFactory.makeConcatWithConstants()
在java11
的版本下获取的字节码如下,可以看出,这里不再存在StringBuilder
,而换成了 invokedynamic #3 <makeConcatWithConstants, BootstrapMethods #0>
关于String
还有一个重要的方法就是intern
,若字符串常量池中能找到这个字符串字面量,则返回该字面量地址,否则则把此
时的String
对象放入常量池,并返回其地址.关于这个方法的验证,字节码就无能为力了,我们得从内存以及jvm源码去分析