String 到底建立了多少個對象

以前有人在羣裏問了這麼一個問題java

若是你和我同樣常常會關注面試題的話,會知道這實際上是一個沒有什麼實際意義的面試題(雖然常常會有人問起,但在實際面試中好像已經沒人會問這個了),甚至早在9年前,R大就已經寫了一篇吐槽這個問題的長文來講明問題。儘管如此,但對於這道題來講,仍是有很多坑的。面試

回到這道題來,當時羣裏也給了好幾個答案,有6個,3個,2個;你也能夠本身先想一想有幾個再來看下面的答案。bash

首先,在類加載時(準確的說是 resolve 階段),JVM會在堆中建立這些字面量對應的字符串對象實例並在字符串常量池中保留其引用,因此須要弄清楚的第一點是,這部分產生的字符串對象是否算這些代碼所產生的。若是不算,那麼答案就很明顯了,就是2個,也就是第2,3行所產生的兩個 String 對象。app

若是認爲 JVM 加載類自動生成的 String 對象也算那段代碼生成的話,那麼生成了多少對象?那麼第二個須要弄清楚的點就是,到底有多少字面量?當時就有羣友認爲是 "a","bc","bcd" 3個對象,由於編譯器自動進行了優化,可真的如此嗎?咱們能夠用 javap 來看一下字節碼:jvm

0: ldc           #2 // String a
2: astore_1
3: new           #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: aload_1
11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc           #6 // String b
16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc           #7 // String c
21: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: astore_1
28: new           #3 // class java/lang/StringBuilder
31: dup
32: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
35: aload_1
複製代碼

這裏我只截取到了第二個對象,能夠發現並非你們想的這樣,並無將 "b" 和 "c" 自動拼接成 "bc",第三個對象也是如此。那麼也就是說,到底生成了多少個字面量對象還和編譯器有關,若是是使用 sun 自帶的 javac (測試時使用的1.8) 編譯器的話,實際上生成了4個,而若是使用其餘優化過的編譯器話,可能就只會生成3個。ide

還有第三個須要注意的是,咱們前面提到了,字面量是在類加載時,就已經生成對象了,也就是說無論有沒有運行到這些代碼,對象都已經生成了,那麼爲何還要討論算上這些對象的狀況?測試

咱們能夠作一個小測試,在第一行代碼處打一個斷點,而後 Debug 模式運行一下,這個時候,這些代碼都尚未運行,而後咱們再用另外一個程序打印一下 StringTable 的信息(也就是字符串常量池的引用表),代碼以下:(PS:能夠用 jps 命令查看須要的進程號)優化

import sun.jvm.hotspot.memory.StringTable;
import sun.jvm.hotspot.runtime.VM;
import sun.jvm.hotspot.tools.Tool;

public class PrintStringTable extends Tool {
    public static void main(String[] args) {
        if (args.length == 0 || args.length > 1) {
            System.err.println("Usage: java PrintStringTable <PID of the JVM whose string table you want to print>");
            System.exit(1);
        }
        PrintStringTable pst = new PrintStringTable();
        pst.execute(args);
        pst.stop();
    }

    @Override
    public void run() {
        VM vm = VM.getVM();
        StringTable table = vm.getStringTable();
        table.stringsDo(instance -> instance.print());
    }
}
複製代碼

在打印出來的信息中,咱們能夠找一下 "a", "b", "c" 的信息,但發現並無找到。而後回到以前的程序,執行一步端點調試,再從新打印 StringTable ,就可以找到 "a" 的信息,也就是說,字面量的字符串確實是在運行完代碼以後才生成的,並非想象中的認爲在加載完畢後就已經有了,這是怎麼回事?ui

實際上,在 JVM規範中明確表面了,resolve 階段是能夠延遲到運行代碼以前的,也就是說,到底有沒有提早生成對象,還和使用的 JVM 有關。spa

總結

因此到底生成了幾個對象,其實一點也不重要,畢竟這是 JVM 的行爲,也不影響你的開發使用,並且涉及到的因素衆多,很難給出標準答案,可是若是能掌握其中的原理,無論從哪一個角度回答都無所謂啦。

相關文章
相關標籤/搜索