跳到主要内容

JEP 250:将内部字符串存储在 CDS 档案中

概括

将驻留字符串存储在类数据共享 (CDS) 档案中。

目标

  • 通过在不同 JVM 进程之间共享String对象和底层数组对象来减少内存消耗。char

  • 仅支持 G1 GC 的共享字符串。共享字符串需要固定区域,G1 是唯一支持固定的 HotSpot GC。

  • 仅支持具有压缩对象和类指针的 64 位平台。

  • 使用常用基准测试时,启动时间、字符串查找时间、GC 暂停时间或运行时性能没有明显下降 (< 2-3%)。

非目标

  • 减少启动时间不是目标。

  • 不支持其他类型的 GC(G1 除外)。

  • 不支持 32 位平台。

动机

目前,当CDS将类存储到存档中时,CONSTANT_String常量池中的项目由UTF-8字符串表示。加载类时,UTF-8 字符串会java.lang.String根据需要转换为对象。这可能会浪费内存,因为每个驻留字符串中的每个字符占用三个字节或更多(在StringUTF-8 中为 2 个字节,在 UTF-8 中为 1-3 个字节)。

此外,由于字符串是动态创建的,因此它们不能轻松地在 JVM 进程之间共享。

描述

在转储时,在堆初始化期间在 Java 堆内分配指定的字符串空间。当写出 interned 字符串表和对象时,修改指向 internedString对象及其底层数组对象的指针,就好像这些对象来自指定空间一样。char``String

字符串表被压缩,然后在转储时存储在存档中。字符串表的压缩技术与共享符号表的压缩技术相同(请参阅JDK-8059510)。常规窄循环编码和解码用于String从压缩字符串表访问共享对象。

在具有压缩 oop 指针的 64 位平台上,窄 oop 是使用窄 oop 基址的偏移量(带或不带缩放)进行编码的。目前有四种不同的编码模式:32 位未缩放、基于零、基于不相交堆和基于堆。根据堆大小和堆最小基数,选择适当的编码模式。转储时和运行时的窄循环编码模式(包括编码移位)必须相同,以便共享字符串空间内的循环指针在运行时保持有效。共享字符串空间可以被认为是在运行时可重定位的,但有限制。不需要将其映射到与转储时相同的地址,但在转储时和运行时,它应该与窄 oop 基址具有相同的偏移量。转储时和运行时的堆大小不需要相同,只要使用相同的编码模式即可。字符串空间的偏移量和 oop 编码模式(和移位)应存储在存档中以供运行时验证。如果编码模式改变,它将使char每个共享的指向数组的 oop 指针的编码无效String。在这种情况下,共享字符串数据将被忽略,而其余共享数据仍可由虚拟机使用。 VM 将报告一条警告,指示由于 GC 配置不兼容而未使用共享字符串。

在运行时,字符串空间被映射为 Java 堆的一部分,其距 oop 编码基数的偏移量与转储时的偏移量相同。映射从存档中保存的字符串空间的最低页对齐地址开始。映射的字符串空间包含共享对象Stringchar数组对象。所有与该映射空间重叠的 G1 区域将被标记为固定;这些 G1 区域不可用于运行时分配。部分重叠的区域中可能会浪费未使用的空间,但在映射的末尾最多应该有一个这样的区域。由于使用相同的窄 oop 编码,因此不需要对字符串空间内的 oop 指针进行修补。共享字符串空间是可写的,但 GC 不应写入该空间中的 oops,以保持不同进程之间的共享性。尝试锁定这些共享字符串之一并因此写入共享空间的应用程序将获得该页面的私有副本,因此失去共享该特定页面的好处。这种情况很少见。

共享字符串表与运行时的常规字符串表不同。查找暂留字符串时会搜索这两个表。共享字符串表在运行时是只读表;不能向其中添加或删除任何条目。

G1 字符串重复数据删除表是一个单独的哈希表,其中包含char运行时用于重复数据删除的数组。当字符串被保留并添加到 时StringTable,该字符串将被重复数据删除,并且基础char数组将被添加到重复数据删除表(如果尚不存在)。重复数据删除表不会存储到存档中。重复数据删除表是在 VM 启动期间使用共享字符串数据填充的。作为一种优化,工作是在G1StringDedupThread(in G1StringDedupThread::run(), after initialize_in_thread()) 中完成的,以减少启动时间。共享字符串的哈希值在转储时预先计算并存储在字符串中,以避免重复数据删除代码在运行时写入哈希值。

测试

此功能的测试将涵盖以下领域:

  • 该功能的基本操作;

  • 与此功能不兼容的模式,例如非 G1 GC 和未压缩的对象/类指针;

  • 转储时和运行时之间普通对象指针编码的变化;

  • 无效的字符串文件格式;

  • 使用此功能时选定的字符串操作,例如驻留和字符串比较;和

  • 使用 GC 诊断模式确保此功能不会导致堆损坏。

依赖关系

需要更新可服务性代理以添加对共享字符串表的支持(请参阅JDK-8079830)。

随着JDK-8054307提出的更改,底层char数组将更改为数组byte。如果集成了 JDK-8054307,则将内部字符串复制到字符串空间并执行重复数据删除的代码将需要反映这一点。影响应该是最小的。