Java中String的intern方法,Java8常量池的位置,为什么String被设计为不可变,javap&cfr.jar反编译,javap反编译后二进制指令代码详解 JVM内存中的常量池的位置 为什么String被设计为不可变?

一个例子

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

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

在执行的时候先设置VM启动参数:

Java中String的intern方法,Java8常量池的位置,为什么String被设计为不可变,javap&cfr.jar反编译,javap反编译后二进制指令代码详解
JVM内存中的常量池的位置
为什么String被设计为不可变?

Java中String的intern方法,Java8常量池的位置,为什么String被设计为不可变,javap&cfr.jar反编译,javap反编译后二进制指令代码详解
JVM内存中的常量池的位置
为什么String被设计为不可变?

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

这里的-XX:-UseGCOverheadLimit关闭GC占用时间过长时报的异常,-Xmx等参数见:https://www.cnblogs.com/theRhyme/p/9120705.html

执行上面的单元测试,等了数十秒:

Java中String的intern方法,Java8常量池的位置,为什么String被设计为不可变,javap&cfr.jar反编译,javap反编译后二进制指令代码详解
JVM内存中的常量池的位置
为什么String被设计为不可变?

说明在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更好的提高性能
  • 线程安全当多线程访问时,不可变对象是线程安全的,不需要什么高深的逻辑解释,如果对象不可变,线程也不能改变它。