• Java中的StringTable常量池


    一. StringTable常量池与串池的关系

    代码:

    public class Demo6 {
        public static void main(String[] args) {
            String s1 = "a";
            String s2 = "b";
            String s3 = "ab";
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    反编译后:

    "C:\Program Files\Java\jdk-11.0.16.1\bin\javap.exe" -v jvm1.Demo6
    Classfile /C:/untitled/out/production/myLife/jvm1/Demo6.class
      Last modified 2022823; size 477 bytes
      MD5 checksum ef11e54131b46d597626bc992d2704d3
      Compiled from "Demo6.java"
    public class jvm1.Demo6
      minor version: 0
      major version: 55
      flags: (0x0021) ACC_PUBLIC, ACC_SUPER
      this_class: #5                          // jvm1/Demo6
      super_class: #6                         // java/lang/Object
      interfaces: 0, fields: 0, methods: 2, attributes: 1
    Constant pool:
       #1 = Methodref          #6.#24         // java/lang/Object."":()V
       #2 = String             #25            // a
       #3 = String             #26            // b
       #4 = String             #27            // ab
       #5 = Class              #28            // jvm1/Demo6
       #6 = Class              #29            // java/lang/Object
       #7 = Utf8               <init>
       #8 = Utf8               ()V
       #9 = Utf8               Code
      #10 = Utf8               LineNumberTable
      #11 = Utf8               LocalVariableTable
      #12 = Utf8               this
      #13 = Utf8               Ljvm1/Demo6;
      #14 = Utf8               main
      #15 = Utf8               ([Ljava/lang/String;)V
      #16 = Utf8               args
      #17 = Utf8               [Ljava/lang/String;
      #18 = Utf8               s1
      #19 = Utf8               Ljava/lang/String;
      #20 = Utf8               s2
      #21 = Utf8               s3
      #22 = Utf8               SourceFile
      #23 = Utf8               Demo6.java
      #24 = NameAndType        #7:#8          // "":()V
      #25 = Utf8               a
      #26 = Utf8               b
      #27 = Utf8               ab
      #28 = Utf8               jvm1/Demo6
      #29 = Utf8               java/lang/Object
    {
      public jvm1.Demo6();
        descriptor: ()V
        flags: (0x0001) ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."":()V
             4: return
          LineNumberTable:
            line 3: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Ljvm1/Demo6;
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: (0x0009) 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 5: 0
            line 6: 3
            line 7: 6
            line 8: 9
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      10     0  args   [Ljava/lang/String;
                3       7     1    s1   Ljava/lang/String;
                6       4     2    s2   Ljava/lang/String;
                9       1     3    s3   Ljava/lang/String;
    }
    SourceFile: "Demo6.java"
    
    Process finished with exit code 0
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 问题分析:
      在这里插入图片描述

    注意:

    1. StringTable属于Hashtable结构,不能扩容
    2. Constant pool中的信息,都会被加载到运行时常量池中,这是a b ab都是常量池中的符号,还没有变为java字符串对象
    3. 例如: 0: ldc #2 会把a符号变为"a"字符串对象(而是用到这行代码才开始创建)此时还要准备StringTable串池[]如果第一次找串池中没有本身对象,那么就要放入串池中为[“a”]。但是如果串池中有,那么只调用
    4. 不是每个字符串对象事先放在字符串池里,而是用到这行代码才开始创建(属于懒惰行为)

    二. StringTable字符串的拼接

    问题分析
    在这里插入图片描述

    • 问:System.out.println(s3==s4);?false
      原因:s3属于串池中,s4属于堆中,虽然值一样,但是位置不一样
      在这里插入图片描述

    代码

    package jvm1;
    public class Demo6 {
        public static void main(String[] args) {
            String s1 = "a";
            String s2 = "b";
            String s3 = "ab";
            String s4 = s1 + s2;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    源码分析

    
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: (0x0009) 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: aload_1
            10: aload_2
            11: invokedynamic #5,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
            16: astore        4
            18: return
          LineNumberTable:
            line 4: 0
            line 5: 3
            line 6: 6
            line 7: 9
            line 9: 18
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      19     0  args   [Ljava/lang/String;
                3      16     1    s1   Ljava/lang/String;
                6      13     2    s2   Ljava/lang/String;
                9      10     3    s3   Ljava/lang/String;
               18       1     4    s4   Ljava/lang/String;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    三. StringTable编译期优化

    代码

    package jvm1;
    public class Demo6 {
        public static void main(String[] args) {
            String s1 = "a";
            String s2 = "b";
            String s3 = "ab";
            String s5="a"+"b";
            //String s4 = s1 + s2;//new StringBuider().append("a").append("b").toString();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    问题分析
    在这里插入图片描述

    • javac 在编译期间的优化,结果已经在编译期间确定为"ab"

    四. StringTable字符串延迟加载

    为了查看字符串及其常量池中的变化,这里我采取了IDEA中的调式功能中的Memory(内存问题)子功能

    1. 调试1
      在这里插入图片描述

    2. 调试2
      在这里插入图片描述

    3. 调试3
      在这里插入图片描述

    • 说明字符串字面量也是(延迟)成为对象的

    五. StringTable特性

    • 常量池中的字符串仅是符号,第一次用到时才变为对象

    • 利用串池的机制,来避免重复创建字符串对象,串池中的字符串有且仅有一个

    • 字符串变量拼接的原理是 StringBuilder (JDK1.8)

    • 字符串常量拼接的原理是编译期优化(编译即确定)

    • 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池

    • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串
      池中的对象返回
      在这里插入图片描述

    • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,
      放入串池, 会把串池中的对象返回

    六. StringTable面试题

    package jvm1;
    
    public class Demo5 {
        public static void main(String[] args) {
            String s1 = "a";
            String s2 = "b";
            String s3 = "a" + "b";//ab
            String s4 = s1 + s2;//new String("ab")
            String s5 = "ab";
            String s6 = s4.intern();//由于串池中存入"ab",所以s4还在指向堆中,但是新生成s6是指向串池中的
    
    // 问
            System.out.println(s3 == s4);//false
            System.out.println(s3 == s5);//true
            System.out.println(s3 == s6);//true
    
            String x2 = new String("c") + new String("d");//new String("cd")
            String x1 = "cd";//"cd"   此时串池中有["cd"]  
            x2.intern();//所以x2返回的值不能放入串池中,仍为堆中
            System.out.println(x1 == x2);//false
            
            
    // 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢 System.out.println(x1 == x2);
            System.out.println(x1 == x2);//true
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    七. StringTable位置

    在这里插入图片描述
    在这里插入图片描述

    PerGen 永久代(JDK1.6)
    在这里插入图片描述

    Metaspace 元空间(JDK1.8)

    • 实例1
      在这里插入图片描述
    • 实例二(解决后)
    • 牵扯的代码
    package cn.itcast.jvm.t1.stringtable;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 演示 StringTable 位置
     * 在jdk8下设置 -Xmx10m -XX:-UseGCOverheadLimit
     * 在jdk6下设置 -XX:MaxPermSize=10m
     */
    public class Demo1_6 {
    
        public static void main(String[] args) throws InterruptedException {
            List<String> list = new ArrayList<String>();
            int i = 0;
            try {
                for (int j = 0; j < 260000; j++) {
                    list.add(String.valueOf(j).intern());
                    i++;
                }
            } catch (Throwable e) {
                e.printStackTrace();
            } finally {
                System.out.println(i);
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    八. StringTable垃圾回收

    演示StringTable垃圾回收

    1. -Xmx10m 设置JVM虚拟机的堆的值
    2. -XX:+PrintStringTableStatistics 打印字符串表的统计信息,可以清楚看到字符串实例的个数和占用的大小信息
    3. XX:+PrintGCDetails -verbose:gc 显示垃圾回收的次数时间等
    • 分析1
      在这里插入图片描述

    • 分析2
      在这里插入图片描述

    • 分析3
      在这里插入图片描述

    通过分析案例中,我们证明了StringTable也会发生垃圾回收

    • 演示的代码
    package cn.itcast.jvm.t1.stringtable;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 演示 StringTable 垃圾回收
     * -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
     */
    public class Demo1_7 {
        public static void main(String[] args) throws InterruptedException {
            int i = 0;
            try {
                for (int j = 0; j < 100000; j++) { // j=100, j=10000
                    String.valueOf(j).intern();
                    i++;
                }
            } catch (Throwable e) {
                e.printStackTrace();
            } finally {
                System.out.println(i);
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    八. StringTable性能调优

    1. 调整 -XX:StringTableSize=桶个数
      案例:
    • 分析1
      在这里插入图片描述

    • 分析2
      在这里插入图片描述

    如果字符串常量个数非常多,那么就可以把-XX:StringTableSize调的非常大,这样就有更好的哈希分布,减少哈希冲突,能够提高性能

    • 代码
    package jvm1;
    
    import java.io.BufferedReader;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
        /*
          演示串池大小对性能的影响
          -Xms500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=1009
         */
        public class Demo7 {
    
            public static void main(String[] args) throws IOException {
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("c:\\a\\sss.txt"), "utf-8"))) {
                    String line = null;
                    long start = System.nanoTime();
                    while (true) {
                        line = reader.readLine();
                        if (line == null) {
                            break;
                        }
                        line.intern();
                    }
                    System.out.println("cost:" + (System.nanoTime() - start) / 1000000);
                }
    
    
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    在这里插入图片描述

    1. 考虑将字符串对象是否入池
  • 相关阅读:
    ZYNQ移植ARM CMSIS_DSP库
    [附源码]java毕业设计学生宿舍管理系统设计
    C++11——lambda表达式
    算法题字符串相关
    1225. 报告系统状态的连续日期
    算法竞赛进阶指南:士兵(Python)
    【C++】GoogleTest进阶之gMock
    SpringBoot - 配置 Filter 的几种方式
    RocketMQ Promethus Exporter
    vue--1.vue开发环境集成、基础指令、属性绑定
  • 原文地址:https://blog.csdn.net/o676448/article/details/126480645