JEP 310: Application Class-Data Sharing | 应用程序类数据共享
摘要
为了提高启动和占用空间的效率,将现有的“类数据共享”(“CDS”)功能扩展到允许应用程序类被放置在共享存档文件中。
目标
- 通过在不同的 Java 进程之间共享常见的类元数据,减少占用空间。
- 改善启动时间。
- 扩展 CDS 以允许从 JDK 运行时映像文件 (
$JAVA_HOME/lib/modules
) 和应用程序类路径中加载归档类到内置平台和系统类加载器中。 - 扩展 CDS 以允许将归档类加载到自定义类加载器中。
非目标
- 在此实现中使用的共享类存档文件格式不会被规范化。
- 在此版本中,CDS 无法归档用户定义模块中的类 (例如
--module-path
指定的类)。我们计划在未来的版本中添加该支持。
成功指标
如果我们能够实现以下两个目标,则该项目将被视为成功:(1) 在多个 JVM 进程中使用 Java 类元数据的内存中实现显著的空间节省,(2) 显著提高启动时间。
举例来说:
- 对于包含 6 个 JVM 进程,共消耗 13GB RAM(其中大约 2GB 是类元数据) 的 Java EE 应用服务器,我们可以节省约 340MB 的 RAM。
- 我们可以将 JEdit 基准测试的启动时间提高 20-30%。
- 我们可以在 4 个 JVM 进程中将嵌入式 Felix 基准测试的 RAM 使用量降低 18%。
上述数据反映了具体的基准测试,并不一定适用于所有情况。这项工作的好处取决于受支持的类加载器加载的类的数量以及整个应用程序堆使用率。
描述
“类数据共享”(Class-Data Sharing)是在 JDK 5 中引入的功能,它允许一组类被预处理成一个共享的归档文件,在运行时可以通过内存映射来减少启动时间。当多个 JVM 共享相同的存档文件时,它还可以减少动态内存占用。
目前,CDS 仅允许引导类加载器加载归档类。应用程序 CDS(“AppCDS”)将 CDS 扩展到允许内置系统类加载器(也称为“app 类加载器”)、内置平台类加载器和自定义类加载器加载已归档的类。
对大型企业应用程序内存使用的分析显示,这些应用程序通常将数万个类加载到应用程序类加载器中。将 AppCDS 应用于这些应用程序将导致每个 JVM 进程节省数百兆字节至数百兆字节的内存。
对无服务器云服务的分析表明,其中许多在启动时加载数千个应用程序类。AppCDS 可以使这些服务快速启动,并提高整个系统的响应时间。
启用 AppCDS
默认情况下,“类数据共享”仅适用于 JVM 的引导类加载器。通过指定命令行选项 -XX:+UseAppCDS
来启用系统类加载器(也称为“app 类加载器”)、平台类加载器和其他用户定义的类加载器的类数据共享。
确定要归档的类
一个应用程序可能会打包大量的类,但在正常操作过程中只使用其中的一小部分。通过仅归档使用的类,我们可以减少文件存储大小和运行时内存使用。为此,请首先使用 -Xshare:off
正常运行应用程序,并使用 -XX:DumpLoadedClassList
命令行选项记录所有已加载的类。
请注意,默认情况下,-XX:DumpLoadedClassList
仅包括由引导类加载器加载的类。您应该指定 -XX:+UseAppCDS
选项,以便包括由系统类加载器和平台类加载器加载的类。例如:
java -Xshare:off -XX:+UseAppCDS -XX:DumpLoadedClassList=hello.lst -cp hello.jar HelloWorld
创建 AppCDS 存档文件
要创建 AppCDS 存档文件,请指定 -Xshare:dump -XX:+UseAppCDS
命令行选项,使用 -XX:SharedClassListFile
选项传递类列表,并将类路径设置为与应用程序使用的相同。您还应该使用 -XX:SharedArchiveFile
选项来指定存储类的存档文件的名称。请注意,如果未指定 -XX:SharedArchiveFile
,则存档的类将存储在 JDK 的安装目录中,这通常不是您想要做的事情。例如:
$ java -Xshare:dump -XX:+UseAppCDS -XX:SharedClassListFile=hello.lst \
-XX:SharedArchiveFile=hello.jsa -cp hello.jar
使用 AppCDS 存档
创建 AppCDS 存档后,您可以在启动应用程序时使用它。通过指定 -Xshare:on -XX:+UseAppCDS
命令行选项,并使用 -XX:SharedArchiveFile
选项指定存档文件的名称来实现。例如:
$ java -Xshare:on -XX:+UseAppCDS -XX:SharedArchiveFile=hello.jsa \
-cp hello.jar HelloWorld
类路径不匹配
使用 -Xshare:dump
选项的类路径必须与使用 -Xshare:on
选项的类路径相同或为其前缀。否则,JVM 将打印关于不匹配类路径的错误消息,并拒绝启动。要分析不匹配,您可以在应用程序的命令行中添加 -Xlog:class+path=info
,JVM 将打印有关预期类路径和实际使用类路径的详细诊断信息。
使用 -Xshare:auto
AppCDS 的工作原理是将存档的内容在固定地址进行内存映射。在某些操作系统上,特别是当启用地址空间布局随机化 (ASLR) 时,当所需的地址空间不可用时,内存映射操作可能会偶尔失败。如果指定了 -Xshare:on
选项,JVM 将将此视为错误条件并无法启动。为了使应用程序在这种情况下更具弹性,我们建议改用 -Xshare:auto
选项。这样,当 JVM 无法内存映射存档时,它将禁用 AppCDS 并继续正常运行应用程序。
请注意,如果存在类路径不匹配,-Xshare:auto
也将禁用 AppCDS。因此,我们建议您首先使用 -Xshare:on
进行测试,以确保没有类路径不匹配,然后在生产环境中使用 -Xshare:auto
。
列出从 AppCDS 存档加载的类
要查找从 AppCDS 存档加载的类,可以使用 -Xlog:class+load=info
命令行选项,该选项会打印出每个加载的类的名称以及从哪里加载的类。从 CDS 存档加载的类将被打印为 source: shared objects file
。例如:
$ java -Xshare:on -XX:+UseAppCDS -XX:SharedArchiveFile=hello.jsa \
-cp hello.jar -Xlog:class+load=info HelloWorld | grep HelloWorld
[0.272s][info][class,load] HelloWorld source: shared objects file
实现
平台和系统类加载器: HotSpot VM 可识别内置的平台和系统类加载器的类加载请求。当这些加载器请求存在于 CDS 存档中的类时,VM 将跳过通常的类文件解析和验证步骤,并加载存档副本的类。
自定义类加载器: 当自定义类加载器调用
ClassLoader::defineClass
时,VM 尝试通过比较类文件数据的指纹与存档类的内容进行匹配。如果找到匹配项,则 VM 将跳过类文件解析和验证步骤,并直接加载存档副本的类。
备选方案
我们曾考虑使用共享内存区域来共享由多个活跃 JVM 进程动态加载的类,但我们发现共享潜力更低且实现更困难。
相反,我们选择使应用程序类数据共享更加静态:
需要额外的 "dump" 步骤。
当应用程序的 JAR 文件更新时,需要重复执行 dump 步骤。
这是在现有的 CDS 基础设施之上构建的,因此实现更简单,并且我们可以在目标用例中实现更高的共享比率。
测试
需要进行广泛的测试以确保兼容性并确认性能优势。
应该在所有支持的平台上进行测试。在某些平台上(特别是 Windows/x86),如果 JVM 由于地址空间布局随机化 (ASLR) 无法映射存档,则测试可能会失败。
风险和假设
AppCDS 曾在 JDK 8 和 JDK 9 的 Oracle JDK 中实施过。这个 JEP 将源代码移动到开放的存储库中,以便使该功能普遍可用。由于 AppCDS 在 JDK 8 和 JDK 9 中经过了广泛测试,因此兼容性和稳定性的风险很低。