【Java 笔记】JVM : StringTable

1.JVM更新:

在这里插入图片描述在这里插入图片描述
JDK1.8将String Table从方法区的常量池移至了堆中。

1.1 String Table移至Heap中的验证

List<String> list =new ArrayList<>();
try {
//不断往串池中添加字符串数据
    for (int j = 0; j < 200000; j++) {
        list.add(String.valueOf(j).intern());
    }
}catch (Throwable e){
    e.printStackTrace();
}
//-Xms10m -Xmx10m 

报错:java.lang.OutOfMemoryError: Java heap space

D:\Application\JDK13\bin\java.exe -Xms10m -Xmx10m "-javaagent:D:\Application\IDEAC\IntelliJ IDEA Community Edition 2019.3.4\lib\idea_rt.jar=63616:D:\Application\IDEAC\IntelliJ IDEA Community Edition 2019.3.4\bin" -Dfile.encoding=UTF-8 -classpath D:\Study\Javacode\out\production\Javacode JvmTest.test1
java.lang.OutOfMemoryError: Java heap space
 at java.base/java.util.Arrays.copyOf(Arrays.java:3721)
 at java.base/java.util.Arrays.copyOf(Arrays.java:3690)
 at java.base/java.util.ArrayList.grow(ArrayList.java:235)
 at java.base/java.util.ArrayList.grow(ArrayList.java:242)
 at java.base/java.util.ArrayList.add(ArrayList.java:452)
 at java.base/java.util.ArrayList.add(ArrayList.java:465)
 at JvmTest.test1.main(test1.java:24)

2. String Table

常量池中的字符串仅仅是字面符号,只有当执行到对应的JVM指令时,才会生成对象,利用串池避免重复创建字符串对象,JDK1.8串池从常量池中移动到Heap中。String table是一个hashtable结构,所以不可能出现两个相同的值,其不可以扩容。

2.1 字节码解释

2.1.1 测试代码

public class StringTableTest {
    public static void main(String[] args) {
        String s1="a";
        String s2="b";
        String s3="ab";
        String s4=s1+s2;
        String s5="a"+"b";
        String s6=s4.intern();
        //判断输出结果?
        System.out.println(s3==s4);
        System.out.println(s3==s5);
        System.out.println(s3==s6);

    }
}

2.1.2 字节码文件

在IDEA的Terminal窗口输入命令: javap -verbose StringTableTest.class
得到字节码文件,截取其中的重要部分:

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=7, args_size=1
         0: ldc           #7                  // String a
         2: astore_1
         3: ldc           #9                  // String b
         5: astore_2
         6: ldc           #11                 // String ab
         8: astore_3
         9: aload_1
        10: aload_2
        11: invokedynamic #13,  0             // InvokeDynamic #0:makeConcatWithC
onstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
        16: astore        4
        18: ldc           #11                 // String ab
        20: astore        5
        22: aload         4
        24: invokevirtual #17                 // Method java/lang/String.intern:(
)Ljava/lang/String;
        27: astore        6
        29: getstatic     #23                 // Field java/lang/System.out:Ljava
/io/PrintStream;
        32: aload_3
        33: aload         4
        35: if_acmpne     42
        38: iconst_1
        39: goto          43
        42: iconst_0
        43: invokevirtual #29                 // Method java/io/PrintStream.print
ln:(Z)V
        46: getstatic     #23                 // Field java/lang/System.out:Ljava
/io/PrintStream;
        49: aload_3
        50: aload         5
        52: if_acmpne     59
        55: iconst_1
        56: goto          60
        59: iconst_0
        60: invokevirtual #29                 // Method java/io/PrintStream.print
ln:(Z)V
        63: getstatic     #23                 // Field java/lang/System.out:Ljava
/io/PrintStream;
        66: aload_3
        67: aload         6
        69: if_acmpne     76
        72: iconst_1
        73: goto          77
        76: iconst_0
        77: invokevirtual #29                 // Method java/io/PrintStream.print
ln:(Z)V
        80: return
      LineNumberTable:
        line 7: 0
        line 8: 3
        line 9: 6
        line 10: 9
        line 11: 18
        line 12: 22
        line 14: 29
        line 15: 46
        line 16: 63
        line 18: 80
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      81     0  args   [Ljava/lang/String;
            3      78     1    s1   Ljava/lang/String;
            6      75     2    s2   Ljava/lang/String;
            9      72     3    s3   Ljava/lang/String;
           18      63     4    s4   Ljava/lang/String;
           22      59     5    s5   Ljava/lang/String;
           29      52     6    s6   Ljava/lang/String;

2.1.3 根据字节码文件理解JVM中StringTable的实现

public static void main(String[] args) {
    // 常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
    String s1="a";
   /* 0: ldc         #7     // String a
      2: astore_1
      // a 符号变为 "a" 字符串对象,并存入局部变量表中Slot=1,变量名为s1的位置
         以"a"作为键值去StringTable中做查询操作,StringTable中还未存有"a",
         于是将"a"存入StringTable中==》StringTable ["a"]
    */

    String s2="b";
    /* 3: ldc        #9     // String b
       5: astore_2
      // b 符号变为 "b" 字符串对象,并存入局部变量表中Slot=2,变量名为s2的位置
         以"b"作为键值去StringTable中做查询操作,StringTable中还未存有"b",
         于是将"b"存入StringTable中==》StringTable ["a","b"]

    */
    String s3="ab";
    /* 6: ldc        #11    // String ab
       8: astore_3
      // ab 符号变为 "ab" 字符串对象,并存入局部变量表中Slot=3,变量名为s3的位置
         同理 ==》StringTable ["a","b","ab"]
    */
    String s4=s1+s2;
   /* 9:  aload_1
      10: aload_2
      11: invokedynamic #13,  0             // InvokeDynamic #0:makeConcatWithC
      onstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
      16: astore   4
      //先加载变量aload_1 ("a" )和aload_2(" b "),再拼接
      //这一步实际等效于:new StringBuilder().append("a").append("b").toString()
      //此处是重新生成了一个新的对象
    */

    String s5="a"+"b";
    /*18: ldc        #11           // String ab
      20: astore     5
      //此处可以发现s5的生成并没有像变量s4一样先加载变量s1和s2
        而是和 String s3="ab" 对应的JVM指令一致,
        所以s5和s3指向同一对象
     */

    String s6=s4.intern();
    //调用intern()方法,先在String table中查找是否存在与该对象相同的字符串,
    // 若存在直接返回String table中字符串的引用,若不存在则将当前对象的引用放到String Table中,并且返回的是当前对象的引用
    //所以此处"ab"在StringTable中已存在,s6获得的是与s3相同的引用地址
    
    //运行结果:
    System.out.println(s3==s4);//false
    System.out.println(s3==s5);//true
    System.out.println(s3==s6);//true

}

2.1.4 String Table编译器调优

String s4=s1+s2;//StringBulider
String s5="a"+"b";//Javac 编译期直接确定

变量拼接是动态的,因为变量的指向在运行期是可以改变的,所以变量的拼接是通过new 一个新的对象实现的。
而常量是确定不变的,所以javac进行了优化,在编译期就确定了常量拼接的结果。

注意:常量的拼接结果会自动存入String Table,而变量的拼接结果需要使用String类的intern()方法存入String Table中。

2.2 Debug测试:对利用串池避免重复创建字符串对象的验证

public static void main(String[] args) {
    String s1="a";
    String s2="b";
    String s3="c";
    String s4="d";
    String s5="e";

    String s6="a";
    String s7="b";
    String s8="c";
    String s9="d";
    String s10="e";
}

首先设置三个断点,进行Debug测试,查看Memory窗口Java,lang.String的数量变化在这里插入图片描述
运行至断点1时,得到初始String的数量:
在这里插入图片描述
运行至断点2时,String如期望增加了5:
在这里插入图片描述
运行至断点3时,String的数量不变,说明没有重复生成"a",“b”,“c”,“d”,“e”:
在这里插入图片描述

2.3 String Table中存储的是String对象还是引用地址?

此部分对于网上查询到的资料有点蒙圈,个人总结如下:JDK1.8开始String Table中存储的既可以是String对象也可以是引用地址(如之后发现此结论有错,再做笔记更改)

2.3.1 Java中创建字符串对象的情况分析:

  1. 采用字面值的方式创建一个字符串时,JVM首先去串池中寻找是否存在该字符串,如果不存在,则在串池中创建该字符串,然后返回该字符串对象的引用地址;如果存在,则不创建任何新的对象,直接返回该字符串对象的引用地址。
  2. 采用new String(“abc”)方式创建字符串时,JVM首先去串池中寻找是否存在该字符串,如果不存在,则首先在串池中创建该字符串,然后再在堆中也创建一个该字符串对象,返回该字符串对象在堆中的引用地址,这种情况实际生成了两个String对象;如果存在,则只在堆中创建新的对象,返回该字符串对象在堆中的引用地址。

2.3.2 代码验证

Debug测试下观察Memory窗口中String数量的变化

断点1执行之后,String增量为1:在这里插入图片描述
断点2执行之后,较上一行代码String增量为2:
在这里插入图片描述

2.3.3 运行结果分析:

public static void main(String[] args) {
    String s1 = "a";
    String s2 = "b";
    String s3=new String("asd");//串池和堆中都会生成一个"asd"字符串对象,而s3指向的是堆中的字符串对象
    String s4=s3.intern();
    //"asd"在串池中已经存在,所以直接返回串池中该字符串对象的引用,而s3指向的是堆中的"asd"
    System.out.println(s4==s3);//false   该结果验证了以上分析

    String s5=s3+s1;//StringBuilder方式创建的拼接对象只在堆中存在
    String s6=s5.intern();//串池中不存在该字符串对象,此时存入的只是s5所指对象的引用地址,所以s6和s5指向的是同一地址
    System.out.println(s5==s6);//true   该结果验证了以上分析
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值