java-方法区 (二) - StringTable串池


StringTable是方法区的重要组成部分

字符串入池

案例:

public class Test2 {
    public static void main(String[] args) {
        String a = "a";
        String b = "b";
        String ab = "ab";
    }
}

编译成class文件后又编译回来

 Last modified 2021-2-21; size 508 bytes
  MD5 checksum 6dfdd4fd20e3dfb96ca5557dca082e97
  Compiled from "Test2.java"
public class com.cdyl.api.Test2
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#25         // java/lang/Object."<init>":()V
   #2 = String             #18            // a
   #3 = String             #20            // b
   #4 = String             #21            // ab
   #5 = Class              #26            // com/cdyl/api/Test2
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/cdyl/api/Test2;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               a
  #19 = Utf8               Ljava/lang/String;
  #20 = Utf8               b
  #21 = Utf8               ab
  #22 = Utf8               MethodParameters
  #23 = Utf8               SourceFile
  #24 = Utf8               Test2.java
  #25 = NameAndType        #7:#8          // "<init>":()V
  #26 = Utf8               com/cdyl/api/Test2
  #27 = Utf8               java/lang/Object
{
  public com.cdyl.api.Test2();
    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 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/cdyl/api/Test2;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=4, 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: return
      LineNumberTable:
        line 7: 0
        line 8: 3
        line 9: 6
        line 10: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  args   [Ljava/lang/String;
            3       7     1     a   Ljava/lang/String;
            6       4     2     b   Ljava/lang/String;
            9       1     3    ab   Ljava/lang/String;
    MethodParameters:
      Name                           Flags
      args
}
SourceFile: "Test2.java"

常量池中的信息,都会被加载到运行时常量池中,这时a b ab 都还是常量池中的符号,还没有变为字符串对象
在这里插入图片描述
当运行到 ldc #2 会把 a 符号变为"a" 字符串对象 这时会去StringTable里面去找一次如果没有就会放入StringTable,如果有了则把StringTable里面的字符串对象返回 ,后面的 ldc #3 ldc #4同理

字符串拼接原理

public class Test2 {
   public static void main(String[] args) {
       String a = "a";
       String b = "b";
       String ab = "ab";
       String s = a + b;
   }
}

同理进行反编译拿到jvm指令

 Last modified 2021-2-21; size 691 bytes
 MD5 checksum a373a0219f010580b0f60dcd3e8cbf1d
 Compiled from "Test2.java"
public class com.cdyl.api.Test2
 minor version: 0
 major version: 52
 flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
  #1 = Methodref          #10.#30        // java/lang/Object."<init>":()V
  #2 = String             #22            // a
  #3 = String             #24            // b
  #4 = String             #25            // ab
  #5 = Class              #31            // java/lang/StringBuilder
  #6 = Methodref          #5.#30         // java/lang/StringBuilder."<init>":()V
  #7 = Methodref          #5.#32         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #8 = Methodref          #5.#33         // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #9 = Class              #34            // com/cdyl/api/Test2
 #10 = Class              #35            // java/lang/Object
 #11 = Utf8               <init>
 #12 = Utf8               ()V
 #13 = Utf8               Code
 #14 = Utf8               LineNumberTable
 #15 = Utf8               LocalVariableTable
 #16 = Utf8               this
 #17 = Utf8               Lcom/cdyl/api/Test2;
 #18 = Utf8               main
 #19 = Utf8               ([Ljava/lang/String;)V
 #20 = Utf8               args
 #21 = Utf8               [Ljava/lang/String;
 #22 = Utf8               a
 #23 = Utf8               Ljava/lang/String;
 #24 = Utf8               b
 #25 = Utf8               ab
 #26 = Utf8               s
 #27 = Utf8               MethodParameters
 #28 = Utf8               SourceFile
 #29 = Utf8               Test2.java
 #30 = NameAndType        #11:#12        // "<init>":()V
 #31 = Utf8               java/lang/StringBuilder
 #32 = NameAndType        #36:#37        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
 #33 = NameAndType        #38:#39        // toString:()Ljava/lang/String;
 #34 = Utf8               com/cdyl/api/Test2
 #35 = Utf8               java/lang/Object
 #36 = Utf8               append
 #37 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
 #38 = Utf8               toString
 #39 = Utf8               ()Ljava/lang/String;
{
 public com.cdyl.api.Test2();
   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 4: 0
     LocalVariableTable:
       Start  Length  Slot  Name   Signature
           0       5     0  this   Lcom/cdyl/api/Test2;

 public static void main(java.lang.String[]);
   descriptor: ([Ljava/lang/String;)V
   flags: ACC_PUBLIC, ACC_STATIC
   Code:
     stack=2, locals=5, 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: return
     LineNumberTable:
       line 6: 0
       line 7: 3
       line 8: 6
       line 9: 9
       line 10: 29
     LocalVariableTable:
       Start  Length  Slot  Name   Signature
           0      30     0  args   [Ljava/lang/String;
           3      27     1     a   Ljava/lang/String;
           6      24     2     b   Ljava/lang/String;
           9      21     3    ab   Ljava/lang/String;
          29       1     4     s   Ljava/lang/String;
   MethodParameters:
     Name                           Flags
     args
}
SourceFile: "Test2.java"

在这里插入图片描述
通过指令给我们的注释我们发现其实底层的是用了StringBuilder的append进行两次拼接,然后通过toString方法转为字符串后返回。
不看注释通过常量池自己定位也是可以的具体看上一篇文章,这里就不进行详细说明
在这里插入图片描述
最终执行的其实是String s= new StringBuilder().append(a).append(b).toString();
而toString是通过新创建一个字符串对象返回
在这里插入图片描述
所以说 ab和s 是不相同的 s是通过new String来的 而ab是在StringTable里的
在这里插入图片描述
代码验证也的确如此

编译期优化

还是先看一个例子
在这里插入图片描述
问ab是否和s是同一个对象?
不清楚还是反编译一下看看对象的常量池

 Last modified 2021-2-21; size 481 bytes
  MD5 checksum 2591a843da775e0d115c1c8ec58d3447
  Compiled from "Test2.java"
public class com.cdyl.api.Test2
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#22         // java/lang/Object."<init>":()V
   #2 = String             #16            // ab
   #3 = Class              #23            // com/cdyl/api/Test2
   #4 = Class              #24            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               LocalVariableTable
  #10 = Utf8               this
  #11 = Utf8               Lcom/cdyl/api/Test2;
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = Utf8               args
  #15 = Utf8               [Ljava/lang/String;
  #16 = Utf8               ab
  #17 = Utf8               Ljava/lang/String;
  #18 = Utf8               s
  #19 = Utf8               MethodParameters
  #20 = Utf8               SourceFile
  #21 = Utf8               Test2.java
  #22 = NameAndType        #5:#6          // "<init>":()V
  #23 = Utf8               com/cdyl/api/Test2
  #24 = Utf8               java/lang/Object
{
  public com.cdyl.api.Test2();
    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 4: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/cdyl/api/Test2;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=3, args_size=1
         0: ldc           #2                  // String ab
         2: astore_1
         3: ldc           #2                  // String ab
         5: astore_2
         6: return
      LineNumberTable:
        line 6: 0
        line 7: 3
        line 8: 6
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  args   [Ljava/lang/String;
            3       4     1    ab   Ljava/lang/String;
            6       1     2     s   Ljava/lang/String;
    MethodParameters:
      Name                           Flags
      args
}
SourceFile: "Test2.java"

在这里插入图片描述
我们查看常量池发现只有一个String ab 那a,b又去哪了啊。
其实java在编译的时候如果发现是2个常量相拼接那么这个值就是确定的
比如

 String s = "a" + "b";

其结果必然是ab那么在编译的时候就会直接编译成ab,我们打开编译过后的class文件用idea自带的反编译打开
在这里插入图片描述
看到的确定是String s = "ab"; 而不是String s = "a" + "b";,那么第一个ab变量被赋值的时候会同时放一份在StringTable中 而s赋值的时候发现StringTable已经有"ab"字符串了,那么就会直接用StringTable中的"ab"进行赋值那么 ab和s 就是同一个对象
在这里插入图片描述

intern() 主动入池

intern方法就是主动把字符串入池,在jdk1.6和jdk1.8返回的有稍许不同,1.6会先看看是否StringTable串池中是否有,如果没有则说明可以入池,那么他会自我复制一份把复制的那份入池,而在1.8中则不会复制而是直接把字符串对象入池

public class Test2 {
    public static void main(String[] args) {
        String a = "a";
        String b = "b";
        String s=a+b;
        String ab = "ab";
        System.out.println(s==ab);
    }
}

前面以前说明了字符串拼接是通过StringBuilder的append进行两次拼接,然后通过toString方法转为字符串后返回,一个是堆里的字符串一个是StringTable串池里的字符串
在这里插入图片描述
所以肯定是false

public class Test2 {
    public static void main(String[] args) {
        String a = "a";
        String b = "b";
        String s=a+b;
        s.intern(); // 入池操作
        String ab = "ab"; // 那么这个ab也是从StringTable拿的
        System.out.println(s==ab); // 那么s和ab就是一个对象
    }
}

我们在ab之前就把s先入池,那么这时ab去常量池中那拿到ab符号转为字符串"ab",然后去StringTable中看发现已经有了那么就会拿StringTable中的返回那么s和ab就是同一个对象
在这里插入图片描述
验证一下的确是true。

现在我们把代码顺序改一下

public class Test2 {
    public static void main(String[] args) {
        String a = "a";
        String b = "b";
        String ab = "ab";
        String s=a+b;
        s.intern();
        System.out.println(s==ab);
    }
}

在这里插入图片描述
这时发现变成了false,这时咋回事啊

public class Test2 {
    public static void main(String[] args) {
        String a = "a";
        String b = "b";
        // ab初始化入池成功
        String ab = "ab";
        String s=a+b;
        // s入池这时发现池中已经有ab了那么入池失败
        s.intern();
        // 所以s和ab就不是同一个对象
        System.out.println(s==ab);
    }
}

前面ab就已经入池成功了,后面s再入池就会失败因为里面已经有"ab"字符串了,所以s和ab就是2个不同的字符串

StringTable的位置

在这里插入图片描述

StringTable大小设置和调优

JVM参数设置: -XX:StringTableSize=60013

因为StringTable是类似于hashTable的结构这个设置的只是设置的哈希桶的大小(类似于数组最大的长度),如果字符串比较多设置小了会导致每个位置的链表过长导致查询插入过慢。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值