本人最近正在面試,然后注意到總是有公司喜歡考String的問題,如字符串連接有幾種方式,它們之間有什么不同等問題;要不就是給一段代碼問創建了幾個對象。那么該不該問呢?我認為當面試有一定工作經驗的求職者時還是應該問問這個問題的,應屆生就不要為難他們了還是多考考底層基礎比較好。下面我結合JVisualVM和javap這兩個工具來詳細解析下JVM內部是怎么處理String對象的。
一、字符串常量池(String Constant Pool)
字符串在java程序中被大量使用,為了避免每次都創建相同的字符串對象及內存分配,JVM內部對字符串對象的創建做了一定的優化,在Permanent Generation中專門有一塊區域用來存儲字符串常量池(一組指針指向Heap中的String對象的內存地址)。
創建字符串對象的幾種形式:
(1)通過new方式如String s = new String("iByteCode")及string.intern()方法
(2)通過字面量的形式如String s = "aaaaa"
(3)字面量+字面量如String s = "bbbb" + "ccccc"
(4)字面量+變量如String s1 = "dddd";String s = "eeeee"+s1
假設剛開始字符串常量池為空,那么對於第一種創建方式,JVM內部是怎么處理的,這里也有一個面試題就是一共創建了幾個對象,在這里答案是兩個,為什么說是兩個呢?一個是字符串字面量本身(可以通過string.intern()方法來取得,下圖中常量池所指向的字符串對象),一個是單獨的字符串對象,Heap視圖如下所示:
看下面的代碼:
public class StringConstantPoolTester { //private String s1 = new String("iByteCode");
public static void main(String[] args) throws Exception { String s1 = new String("iByteCode"); System.out.println(s1); CyclicBarrier barrier = new CyclicBarrier(2); barrier.await(); } }
那么怎么來驗證上面的結論的正確性呢?我們可以通過JVisualVM來Heap dump功能來實現,通過OQL語言來查詢Heap內值為iByteCode的字符串對象的個數就可以確定上面的代碼到底創建了幾個對象。執行結果如下圖所示:
這里有一點要注意,對於通過new方式創建的String對象,每次都會在Heap上創建一個新的實例,但是對於字符串字面量的形式,只有當字符串常量池中不存在相同對象時才會創建。
第二種方式不用說,相當於第一種方式中的字面量部分。
第三種和第四種方式會怎樣創建字符串對象,可以通過javap和JVisualVM來驗證,下面通過一段代碼來驗證:
public class StringConstantPoolTester { //private String s1 = new String("iByteCode");
public static void main(String[] args) throws Exception { String s1 = new String("iByteCode"); String s2 = "bbbb" + "ccccc"; String s3 = "dddd" + s2; System.out.println(s3); CyclicBarrier barrier = new CyclicBarrier(2); barrier.await(); } }
這段代碼的bytecode輸出如下:
對於第三種形式String s2 = "bbbb" + "ccccc",在main方法字節碼的第10-12可以看到在JVM里直接通過ldc指令將指向bbbbccccc字符串字面量的引用的值放入到Operand Stack頂,然后存入到Local variable Array的第二個slot位。同時可以通過JVisualVM驗證結論的正確性,由於篇幅問題這里省略。
對於第四種形式String s3 = "dddd" + s2,在main方法字節碼的13-32可以看到在JVM里面創建了兩個字符串字面量dddd和ddddbbbbccccc,並且調用StringBuilder對字符串進行連接。
二、參考資料:
http://theopentutorials.com/tutorials/java/strings/string-literal-pool/