JVM--StringTable字符串常量池

  • 结构

常量池中的信息都会被加载到运行时常量池中。

String table又称为String pool,字符串常量池,其存在于堆中(jdk1.7之后改的)。最重要的一点,String table中存储的并不是String类型的对象,存储的而是指向String对象的索引,真实对象还是存储在堆中。

此外String table还存在一个hash表的特性,里面不存在相同的两个字符串。

此外String对象调用intern()方法时,会先在String table中查找是否存在于该对象相同的字符串,若存在直接返回String table中字符串的引用,若不存在则在String table中创建一个与该对象相同的字符串。

  • 代码和字节码解释: 

package com.zeng;

public class StringTableTest {

    public static void main(String[] args) {
        String s1="a";
        /**
         *  0: ldc           #2 // String a 把a符号变为"a"字符串对象
         *  2: astore_1         //将其放入本地变量表中slot为1的位置:  3      55     1    s1   Ljava/lang/String;
         */

        String s2="b";
        /**
         *  3: ldc           #3 // String b 把b符号变为"b"字符串对象
         *  5: astore_2         //将其放入本地变量表中slot为2的位置:  6      52     2    s2   Ljava/lang/String;
         */

        String s3="a"+"b";
        /**
         * 6: ldc           #4 // String ab 把ab符号变为"ab"字符串对象
         * 8: astore_3         //将其放入本地变量表中slot为3的位置:   9      49     3    s3   Ljava/lang/String;
         */

        String s4=s1+s2;
        /**
         *9: new           #5  // class java/lang/StringBuilder 新建一个StringBuilder对象
         *12: dup
         *13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V  执行StringBuilder的构造函数
         *16: aload_1          //获取本地变量表中slot为1位置压入的字符串池中的值:a
         *17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
         *                        调用StringBuilder.append("a")
         *20: aload_2          //获取本地变量表中slot为1位置压入的字符串池中的值:b
         *21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
         *                        调用StringBuilder.append("b")
         *24: invokevirtual #8 // Method jav/lang/StringBuilder.toString:()Ljava/lang/String;
         *                        调用StringBuilder.toString(),源码:return new String(value, 0, count);
         *                        也就是在堆中重新申请一块内存,返回其引用地址
         *27: astore        4   //将其放入本地变量表中slot为4的位置:29      29     4    s4   Ljava/lang/String;
         */
        String s5="ab";
        String s6="a"+"b";
        /**
         * 29: ldc           #4  // String ab 把ab符号变为"ab"字符串对象
         * 31: astore    5  //将其放入本地变量表中slot为5的位置:33      25     5    s5   Ljava/lang/String;
         */

        String s7=s4.intern();
        /**
         *33: aload         4   //获取本地变量表中slot为4位置压入的字符串池中的值:ab
         *35: invokevirtual #9  // Method java/lang/String.intern:()Ljava/lang/String;
         *                      调用String.intern()方法:
         *                      编译器会将字符串添加到常量池中(stringTable维护),并返回指向该常量的引用。
         *38: astore        6   //将其放入本地变量表中slot为5的位置:40      18     6    s6   Ljava/lang/String;
         */

        System.out.println(s3==s4);
        /**
         * 分析:false
         *  s3会将字符串"ab"放入到字符串常量池中,而s4是在堆中,所以不相等
         */

        System.out.println(s5==s6);
        /**
         * 分析:true
         *  javac在编译时 "a"+"b" 会编译成 "ab"
         */
    }
}
  • 字节码

javap -v StringTableTest.class 
Classfile /Users/zengyunhua/Documents/myWork/spring-boot-starter-demo/testProject3/target/classes/com/zeng/StringTableTest.class
  Last modified 2020-3-20; size 1110 bytes
  MD5 checksum 319f6b9473d86cec4458ea5f770d29cb
  Compiled from "StringTableTest.java"
public class com.zeng.StringTableTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #13.#39        // java/lang/Object."<init>":()V
   #2 = String             #40            // a
   #3 = String             #41            // b
   #4 = String             #42            // ab
   #5 = Class              #43            // java/lang/StringBuilder
   #6 = Methodref          #5.#39         // java/lang/StringBuilder."<init>":()V
   #7 = Methodref          #5.#44         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #8 = Methodref          #5.#45         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #9 = Methodref          #46.#47        // java/lang/String.intern:()Ljava/lang/String;
  #10 = Fieldref           #48.#49        // java/lang/System.out:Ljava/io/PrintStream;
  #11 = Methodref          #50.#51        // java/io/PrintStream.println:(Z)V
  #12 = Class              #52            // com/zeng/StringTableTest
  #13 = Class              #53            // java/lang/Object
  #14 = Utf8               <init>
  #15 = Utf8               ()V
  #16 = Utf8               Code
  #17 = Utf8               LineNumberTable
  #18 = Utf8               LocalVariableTable
  #19 = Utf8               this
  #20 = Utf8               Lcom/zeng/StringTableTest;
  #21 = Utf8               main
  #22 = Utf8               ([Ljava/lang/String;)V
  #23 = Utf8               args
  #24 = Utf8               [Ljava/lang/String;
  #25 = Utf8               s1
  #26 = Utf8               Ljava/lang/String;
  #27 = Utf8               s2
  #28 = Utf8               s3
  #29 = Utf8               s4
  #30 = Utf8               s5
  #31 = Utf8               s6
  #32 = Utf8               s7
  #33 = Utf8               StackMapTable
  #34 = Class              #24            // "[Ljava/lang/String;"
  #35 = Class              #54            // java/lang/String
  #36 = Class              #55            // java/io/PrintStream
  #37 = Utf8               SourceFile
  #38 = Utf8               StringTableTest.java
  #39 = NameAndType        #14:#15        // "<init>":()V
  #40 = Utf8               a
  #41 = Utf8               b
  #42 = Utf8               ab
  #43 = Utf8               java/lang/StringBuilder
  #44 = NameAndType        #56:#57        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #45 = NameAndType        #58:#59        // toString:()Ljava/lang/String;
  #46 = Class              #54            // java/lang/String
  #47 = NameAndType        #60:#59        // intern:()Ljava/lang/String;
  #48 = Class              #61            // java/lang/System
  #49 = NameAndType        #62:#63        // out:Ljava/io/PrintStream;
  #50 = Class              #55            // java/io/PrintStream
  #51 = NameAndType        #64:#65        // println:(Z)V
  #52 = Utf8               com/zeng/StringTableTest
  #53 = Utf8               java/lang/Object
  #54 = Utf8               java/lang/String
  #55 = Utf8               java/io/PrintStream
  #56 = Utf8               append
  #57 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #58 = Utf8               toString
  #59 = Utf8               ()Ljava/lang/String;
  #60 = Utf8               intern
  #61 = Utf8               java/lang/System
  #62 = Utf8               out
  #63 = Utf8               Ljava/io/PrintStream;
  #64 = Utf8               println
  #65 = Utf8               (Z)V
{
  public com.zeng.StringTableTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/zeng/StringTableTest;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=8, args_size=1
         0: ldc           #2                  // String a
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
         9: new           #5                  // class java/lang/StringBuilder
        12: dup
        13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
        16: aload_1
        17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        20: aload_2
        21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: astore        4
        29: ldc           #4                  // String ab
        31: astore        5
        33: ldc           #4                  // String ab
        35: astore        6
        37: aload         4
        39: invokevirtual #9                  // Method java/lang/String.intern:()Ljava/lang/String;
        42: astore        7
        44: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
        47: aload_3
        48: aload         4
        50: if_acmpne     57
        53: iconst_1
        54: goto          58
        57: iconst_0
        58: invokevirtual #11                 // Method java/io/PrintStream.println:(Z)V
        61: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
        64: aload         5
        66: aload         6
        68: if_acmpne     75
        71: iconst_1
        72: goto          76
        75: iconst_0
        76: invokevirtual #11                 // Method java/io/PrintStream.println:(Z)V
        79: return
      LineNumberTable:
        line 6: 0
        line 12: 3
        line 18: 6
        line 24: 9
        line 40: 29
        line 41: 33
        line 47: 37
        line 56: 44
        line 62: 61
        line 67: 79
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      80     0  args   [Ljava/lang/String;
            3      77     1    s1   Ljava/lang/String;
            6      74     2    s2   Ljava/lang/String;
            9      71     3    s3   Ljava/lang/String;
           29      51     4    s4   Ljava/lang/String;
           33      47     5    s5   Ljava/lang/String;
           37      43     6    s6   Ljava/lang/String;
           44      36     7    s7   Ljava/lang/String;
      StackMapTable: number_of_entries = 4
        frame_type = 255 /* full_frame */
          offset_delta = 57
          locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
          stack = [ class java/io/PrintStream ]
        frame_type = 255 /* full_frame */
          offset_delta = 0
          locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
          stack = [ class java/io/PrintStream, int ]
        frame_type = 80 /* same_locals_1_stack_item */
          stack = [ class java/io/PrintStream ]
        frame_type = 255 /* full_frame */
          offset_delta = 0
          locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
          stack = [ class java/io/PrintStream, int ]
}
SourceFile: "StringTableTest.java"

注意事项 

  1. 常量池中的字符串公是符号,第一次使用时才会变成对象。
  2. new String都是在堆上创建字符串对象。当调用 intern() 方法时,编译器会将字符串添加到常量池中(stringTable维护),并返回指向该常量的引用。
  3. 通过字面量赋值创建字符串(如:String str=”twm”)时,会先在常量池中查找是否存在相同的字符串,若存在,则将栈中的引用直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。
  4. 常量字符串的“+”操作,编译阶段直接会合成为一个字符串。如string str=”JA”+”VA”,在编译阶段会直接合并成语句String str=”JAVA”,于是会去常量池中查找是否存在”JAVA”,从而进行创建或引用。
  5. 对于final字段,编译期直接进行了常量替换(而对于非final字段则是在运行期进行赋值处理的)。
    final String str1=”ja”;
    final String str2=”va”;
    String str3=str1+str2;
    在编译时,直接替换成了String str3=”ja”+”va”,根据第三条规则,再次替换成String str3=”JAVA”
  6. 常量字符串和变量拼接时(如:String str3=baseStr + “01”;)会调用stringBuilder.append()在堆上创建新的对象
  7. 可以使用String.intern()方法,主动将串池中还没有的字符串(动态创建的)对象放入串池。JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。简单的说,就是往常量池放的东西变了:原来在常量池中找不到时,1.6会复制一个副本放到常量池,1.7后则是将在堆上的地址引用复制到常量池
public class StringTableTest2 {
    public static void main(String[] args) {
        String s=new String("a")+new String("b");
        String s1=s.intern();

        System.out.println(s1=="ab");
        System.out.println(s=="ab");
    }
}

1.6运行结果为:true和false

1.7运行结果为:true和true 

参考链接:https://blog.csdn.net/tyyking/article/details/82496901

 

调优:

  1. 调整-XX:StringTableSize=桶个数
  2. 如果程序中有大量的字符串,而且重复的比较多,可以考虑使用intern()方法,把字符串入池,这样可以去掉重复的,减少内存占用

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值