从字节码看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.

我们拿出这段代码的字节码形态

image-20211021170721026

只有两个new指令,根据对称性很容易看出来,0-9是第一行代码,10-19是第二行代码,而关于我们所说的字符串字面量却并没有在这里被创建,而只是从常量池中通过ldc加载过来,也就是说,如果单纯的只说这两行代码的执行阶段创建了几个对象,应该说只创建了两个,但是如果说涉及了几个对象,那当然还是三个.

编译期优化

我们再来看一段代码

String a = "a";
String ab = a+"b";

我们知道,若是"a"+"b"那么在编译的时候就会自动优化为"ab",但如果是a+"b"则不会如此,为什么呢?笔者认为,之所以前者可以被优化,是因为在编译期是已知的,但换做引用就说不准了,不运行,就不会知道最终所引用的对象是什么样子的,因此不能如此优化.那这里可以说创建了几个对象呢? 我们说,有几个new,就创建了几个对象,这里创建了一个StringBuilder,以及一个String对象,但这里只有一个new,另一个new在那个toString方法中.

image-20211021180836733

与之作为对比将a声明为final

final String a = "a";
String ab = a+"b";

则可以在编译期完成优化,字节码如下

image-20211021182046404

StringBuilder创建

再让我们回到前一份字节码,可以验证a+"b"的这个连接操作实质上确实如网上所说是通过一个StringBuilder实例来完成的.

我们再通过字节码来验证应该自己手动创建StringBuilder这句话的正确性

String c = "";
for(int i =1;i<4;i++){
        c+=i;
}

image-20211021183256964

图中红色区域即为循环体,可以看到,隐式使用StringBuilder是会在每次迭代时都创建一个新的实例,因此会导致效率降低.

但值得注意的是,在java11中,就不是采用的StringBuilder了,而是StringConcatFactory.makeConcatWithConstants()

java11的版本下获取的字节码如下,可以看出,这里不再存在StringBuilder,而换成了 invokedynamic #3 <makeConcatWithConstants, BootstrapMethods #0>

image-20211021185141436

关于String还有一个重要的方法就是intern,若字符串常量池中能找到这个字符串字面量,则返回该字面量地址,否则则把此

时的String对象放入常量池,并返回其地址.关于这个方法的验证,字节码就无能为力了,我们得从内存以及jvm源码去分析

posted @ 2021-10-21 19:27  茕祇  阅读(86)  评论(0编辑  收藏  举报