一个例子
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, 因为,如果常量池中存在当前字符串,就会直接返回当前字符串;如果常量池中没有此字符串,会将此字符串放入常量池中后,再返回。
后续把上面的猜想挨着验证;
先编译: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
为什么String被设计为不可变?
https://www.cnblogs.com/wuchanming/p/9201103.html
- 安全首要原因是安全,不仅仅体现在你的应用中,而且在JDK中,Java的类装载机制通过传递的参数(通常是类名)加载类,这些类名在类路径下,想象一下,假设String是可变的,一些人通过自定义类装载机制分分钟黑掉应用。如果没有了安全,Java不会走到今天
- 性能 string不可变的设计出于性能考虑,当然背后的原理是string pool,当然string pool不可能使string类不可变,不可变的string更好的提高性能。
- 线程安全当多线程访问时,不可变对象是线程安全的,不需要什么高深的逻辑解释,如果对象不可变,线程也不能改变它。