Java中的String,javap&cfr.jar反編譯,javap反編譯後二進位制指令程式碼詳解,Java8常量池的位置
一個例子
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 } }
結果分析:
c==d是true,是因為d="a"+"b"是兩個常量String的物件相加,返回的String物件就是常量String;
b==c是false,是因為b=a+“b”,java 過載了“+”,反編譯java位元組碼可以發現“+”其實是呼叫了StringBuilder 所以使用了“+”其實是生成了一個新的物件;
b.intern() == c 是true, 因為,String物件呼叫intern方法後,會從常量池中找出與b通過equals()方法true的常量字串物件。
後續把上面的猜想挨著驗證;
先編譯: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反編譯引數):
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等引數見:https://www.cnblogs.com/theRhyme/p/9120705.html
執行上面的單元測試,等了數十秒:
說明在Java8中,字串常量池在JVM的堆中。參考:https://blog.csdn.net/u014039577/article/details/50377805