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


    一个例子

    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更好的提高性能
    • 线程安全当多线程访问时,不可变对象是线程安全的,不需要什么高深的逻辑解释,如果对象不可变,线程也不能改变它。 
  • 相关阅读:
    关于 setColorFilter 和 PorterDuff.Mode
    下拉刷新 SwipRefreshLayout
    from athletelist import AthleteList出现红色下滑波浪线警告
    IndentationError: unindent does not match any outer indentation level
    定制数据对象2
    定制数据对象
    数据处理02
    数据处理
    python 工具箱
    嵌套列表的格式打印
  • 原文地址:https://www.cnblogs.com/theRhyme/p/10216316.html
Copyright © 2020-2023  润新知