![](https://i-blog.csdnimg.cn/blog_migrate/7c8f95bb595a7d008d57c8ed4ef2dd87.png)
本期目录
1. StringTable简介
- StringTable 在 1.8 及之后放在了堆中,但逻辑上还是运行时常量池的一部分。
- 数据结构是 Hash 表。
2. StringTable特性
- 常量池中的字符串仅是符号,第一次用到时才变为对象。
- 利用字符串池的机制,来避免重复创建字符串对象。
- 字符串变量拼接的原理是 StringBuilder (1.8) 。
- 字符串常量拼接的原理是编译期优化。
- 可以使用
intern()
方法,主动将字符串池中还没有的字符串对象放入串池。- 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回。
- 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制 (两份是不同的对象 ) 一份,放入串池,会把串池中的对象返回。
3. StringTable位置
-
1.6 及以前位于方法区的运行时常量池中,1.7 及之后位于堆中。
-
这样调整的目的是:因为 1.6 中,HotSpot 虚拟机的方法区是通过永久代来实现。永久代 GC 的效率很低,而 Java 中字符串的使用是非常频繁的。这样就会导致一些没有引用指向的字符串不能被及时回收,从而造成了大量的内存占用。
-
从 1.7 开始,StringTable 就挪到了堆中,堆的 GC 效率比永久代高。这样StringTable 中没有引用的字符串就能被及时回收,从而减轻了字符串对内存的占用。
4. StringTable垃圾回收
4.1 StringTable垃圾回收相关虚拟机参数
-
本节需要掌握的虚拟机参数:
虚拟机参数 作用 -Xmx10m
堆(Heap)内存大小设置为10MB -XX:+PrintStringTableStatistics
打印字符串常量池的统计信息 -XX:+PrintGCDetails
打印垃圾回收的详细信息 -verbose:gc
打印垃圾回收的详细信息 如果新版 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); } } }
输出:
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 统计信息中
entries
和literals
的个数变化。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
-
可以看到,程序运行后 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
-
StringTable 里本应有 21793 个字符串对象的,但是此时显示只有 4706 个,此时 StringTable 空间占用来到了 915KB 。消失的字符串对象都是被虚拟机垃圾回收掉了 。可以看到顶部垃圾回收的统计信息中赫然写道:
Allocation Failure
,内存空间分配失败。因此触发了垃圾回收,后面是回收的内存信息变化以及垃圾回收所用时间。可以看到回收用时很少,这是因为回收的是 “新生代”PSYoungGen
的堆空间内存,新生代堆内存回收速度很快。 -
通过上面的例子可以证明, StringTable 字符串常量池中也是会发生垃圾回收的。
5. IDEA2022设置虚拟机参数
-
IDEA 2022.2.3 突然找不到哪里可以设置虚拟机参数了。找了一会儿找到了。
-
点击 “Edit Configurations…” :
-
然后点击 “Modify options” 下的 “Add VM options” 即可。
-
虚拟机参数输入栏就出来了,多个虚拟机参数使用空格分隔。