【JVM】StringTable字符串常量池详解



1. StringTable简介

  • StringTable 在 1.8 及之后放在了堆中,但逻辑上还是运行时常量池的一部分。
  • 数据结构是 Hash 表。

2. StringTable特性

  • 常量池中的字符串仅是符号,第一次用到时才变为对象。
  • 利用字符串池的机制,来避免重复创建字符串对象。
  • 字符串变量拼接的原理是 StringBuilder (1.8) 。
  • 字符串常量拼接的原理是编译期优化。
  • 可以使用 intern() 方法,主动将字符串池中还没有的字符串对象放入串池。
    • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回。
    • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制 (两份是不同的对象 ) 一份,放入串池,会把串池中的对象返回。

3. StringTable位置

  • 1.6 及以前位于方法区的运行时常量池中,1.7 及之后位于堆中。

    image-20221011120830774

  • 这样调整的目的是:因为 1.6 中,HotSpot 虚拟机的方法区是通过永久代来实现。永久代 GC 的效率很低,而 Java 中字符串的使用是非常频繁的。这样就会导致一些没有引用指向的字符串不能被及时回收,从而造成了大量的内存占用。

  • 从 1.7 开始,StringTable 就挪到了堆中,堆的 GC 效率比永久代高。这样StringTable 中没有引用的字符串就能被及时回收,从而减轻了字符串对内存的占用。


4. StringTable垃圾回收


4.1 StringTable垃圾回收相关虚拟机参数

  • 本节需要掌握的虚拟机参数:

    虚拟机参数作用
    -Xmx10m堆(Heap)内存大小设置为10MB
    -XX:+PrintStringTableStatistics打印字符串常量池的统计信息
    -XX:+PrintGCDetails打印垃圾回收的详细信息
    -verbose:gc打印垃圾回收的详细信息

    image-20221016211052298

    如果新版 IDEA 没有虚拟机参数输入栏,请参考《5. IDEA2022设置虚拟机参数》设置。

  • 我们先运行一段空代码来查看添加这些虚拟机参数的效果:

    public class Demo01 {
        public static void main(String[] args) {
            int i = 0;
            try {
                
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println(i);
            }
        }
    }
    

    输出:

    image-20221017092055033


4.2 StringTable底层解析

  • StringTable 底层是使用 Hash 表来实现的。Hash 表就是数组 + 链表 (类似于 HashMap) 。
  • 每个数组元素就称为一个 ”桶“ (bucket) 。StringTable 中默认大小为 60013 个。
  • 存储的字符串对象个数称为 entries
  • 字符串常量 (字面量) 的个数称为 literals ,其大小值一般与上面的 entries 相同。
  • StringTable 总占用空间为 Total footprint

4.3 探索StringTable中的个数变化

  • 从上图可以看到,代码还没执行任何操作,StringTable 里就已经有 1693 个字符串对象了。这是为什么呢?因为 Java 程序运行时,其类名、方法名等也是以字符串常量的形式储存在 StringTable 中。

  • 现在我们编写程序让程序主动把 100 个字符串对象加入到 StringTable 中,再来查看 StringTable 统计信息中 entriesliterals 的个数变化。

    public static void main(String[] args) {
        int i = 0;
        try {
            // 循环一百次加入StringTable中
            for (int j = 0; j < 100; j++) {
                String.valueOf(j).intern();
                i++;	// 计数器
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println(i);
        }
    }
    

    输出:

    100
    

    image-20221017093202819

  • 可以看到,程序运行后 StringTable 中存储的字符串对象的个数从原来的 1693 个上升到 1793 个,正好多了 100 个。这 100 个就是我们程序添加的字符串对象。且 StringTable 总占用已经来到 681KB 。

  • 接下来我们往 StringTable 中加入两万个字符串对象,试图撑爆堆内存 10 MB 的空间,以此强迫虚拟机触发垃圾回收。

    public static void main(String[] args) {
        int i = 0;
        try {
            // 循环两万次加入StringTable中
            for (int j = 0; j < 20000; j++) {
                String.valueOf(j).intern();
                i++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println(i);
        }
    }
    

    输出:

    20000
    

    image-20221017094530904

  • StringTable 里本应有 21793 个字符串对象的,但是此时显示只有 4706 个,此时 StringTable 空间占用来到了 915KB 。消失的字符串对象都是被虚拟机垃圾回收掉了 。可以看到顶部垃圾回收的统计信息中赫然写道:

    image-20221017094819784

    Allocation Failure ,内存空间分配失败。因此触发了垃圾回收,后面是回收的内存信息变化以及垃圾回收所用时间。可以看到回收用时很少,这是因为回收的是 “新生代” PSYoungGen 的堆空间内存,新生代堆内存回收速度很快。

  • 通过上面的例子可以证明, StringTable 字符串常量池中也是会发生垃圾回收的。


5. IDEA2022设置虚拟机参数

  • IDEA 2022.2.3 突然找不到哪里可以设置虚拟机参数了。找了一会儿找到了。

  • 点击 “Edit Configurations…” :

    image-20221016210226039

  • 然后点击 “Modify options” 下的 “Add VM options” 即可。
    image-20221016210724838

  • 虚拟机参数输入栏就出来了,多个虚拟机参数使用空格分隔。
    image-20221016210841507

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卡皮巴拉不躺平

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值