一个例子
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