Java中String的intern方法,Java8常量池的位置,爲何String被設計爲不可變,javap&cfr.jar反編譯,javap反編譯後二進制指令代碼詳解

一個例子html

 

public class TestString{
    public static void main(String[] args){
            String a = "a";
            String b = a+"b";
            String c = "ab";
            String d = "a" + "b";
            System.out.println(c == d);           //true!!!!
            System.out.println(c == d.intern());  //true
            System.out.println(b == c);           //false
            System.out.println(b.intern() == c);  //true!!!
            System.out.println(b == c.intern());  //false
            System.out.println(b == d);           //false
    }
 }

 

 

 

結果分析:java

c==d是true,是由於d="a"+"b"是兩個常量String的對象相加,返回的String對象就是常量String;安全

b==c是false,是由於b=a+「b」,java 重載了「+」,反編譯java字節碼能夠發現「+」實際上是調用了StringBuilder 因此使用了+」實際上是生成了一個新的對象;多線程

b.intern() == c 是true, 由於,若是常量池中存在當前字符串,就會直接返回當前字符串;若是常量池中沒有此字符串,會將此字符串放入常量池中後,再返回app

後續把上面的猜測挨着驗證;異步

 

先編譯:javac TestString.java;性能

在使用cfr-0.137.jar將java反編譯:java -jar cfr-0.137.jar TestString.class --stringbuilder false ;
反編譯結果以下:
單元測試

 

/*
 * Decompiled with CFR 0.137.
 */
import java.io.PrintStream;

public class TestString {
    public static void main(String[] arrstring) {
        String string = "a";
        String string2 = new StringBuilder().append(string).append("b").toString();
        String string3 = "ab";
        String string4 = "ab";
        System.out.println(string3 == string4);
        System.out.println(string3 == string4.intern());
        System.out.println(string2 == string3);
        System.out.println(string2.intern() == string3);
        System.out.println(string2 == string3.intern());
        System.out.println(string2 == string4);
    }
}

 

能夠看到,兩個字符串相加,若是其中一個不是常量字符串(即不是經過變量名引用的字符串如"LuoTiany"),那麼編譯器編譯後,JVM執行就會經過new StringBuilder對象操做,若是在一個循環中,就會產生多個StringBuilder對象,因此字符串相加,儘可能使用StringBuilder對象的append(異步)或StringBuffer對象的append(同步);測試

 

使用javap -c TestString.class反編譯結果以下(javap反編譯參數):ui

Compiled from "TestString.java"
public class TestString {
  public TestString();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       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: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      22: astore_2
      23: ldc           #8                  // String ab
      25: astore_3
      26: ldc           #8                  // String ab
      28: astore        4
      30: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      33: aload_3
      34: aload         4
      36: if_acmpne     43
      39: iconst_1
      40: goto          44
      43: iconst_0
      44: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
      47: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      50: aload_3
      51: aload         4
      53: invokevirtual #11                 // Method java/lang/String.intern:()Ljava/lang/String;
      56: if_acmpne     63
      59: iconst_1
      60: goto          64
      63: iconst_0
      64: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
      67: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      70: aload_2
      71: aload_3
      72: if_acmpne     79
      75: iconst_1
      76: goto          80
      79: iconst_0
      80: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
      83: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      86: aload_2
      87: invokevirtual #11                 // Method java/lang/String.intern:()Ljava/lang/String;
      90: aload_3
      91: if_acmpne     98
      94: iconst_1
      95: goto          99
      98: iconst_0
      99: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
     102: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
     105: aload_2
     106: aload_3
     107: invokevirtual #11                 // Method java/lang/String.intern:()Ljava/lang/String;
     110: if_acmpne     117
     113: iconst_1
     114: goto          118
     117: iconst_0
     118: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
     121: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
     124: aload_2
     125: aload         4
     127: if_acmpne     134
     130: iconst_1
     131: goto          135
     134: iconst_0
     135: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
     138: return
}

 上面若是看不懂,能夠在這裏找,javap編譯後二進制指令代碼詳解:http://www.blogjava.net/DLevin/archive/2011/09/13/358497.html#Post

 

 

JVM內存中的常量池的位置

@Test
    public void constantPool(){
        List<String> list = new ArrayList<String>();
        int i=0;
        while(true){
            list.add(String.valueOf(i++).intern());
        }
    }

在執行的時候先設置VM啓動參數:

在原來的VM options :-ea 後面追加-Xmx128m -Xms64m -Xmn32m -Xss16m -XX:-UseGCOverheadLimit;

這裏的-XX:-UseGCOverheadLimit關閉GC佔用時間過長時報的異常,-Xmx等參數見:http://www.javashuo.com/article/p-qaprakcz-gp.html

執行上面的單元測試,等了數十秒:

說明在Java8中,字符串常量池在JVM的堆中。參考:https://blog.csdn.net/u014039577/article/details/50377805

 

爲何String被設計爲不可變?

http://www.javashuo.com/article/p-uukdvibl-ba.html

  • 安全首要緣由是安全,不單單體如今你的應用中,並且在JDK中,Java的類裝載機制經過傳遞的參數(一般是類名)加載類,這些類名在類路徑下,想象一下,假設String是可變的,一些人經過自定義類裝載機制分分鐘黑掉應用。若是沒有了安全,Java不會走到今天
  • 性能 string不可變的設計出於性能考慮,固然背後的原理是string pool,固然string pool不可能使string類不可變,不可變的string更好的提升性能
  • 線程安全當多線程訪問時,不可變對象是線程安全的,不須要什麼高深的邏輯解釋,若是對象不可變,線程也不能改變它。 
相關文章
相關標籤/搜索