跳到主要内容

JEP 310:应用程序类数据共享

概括

为了改善启动和占用空间,请扩展现有的类数据共享(“CDS”)功能,以允许将应用程序类放置在共享存档中。

目标

  • 通过在不同的 Java 进程之间共享公共类元数据来减少占用空间。

  • 改善启动时间。

  • 扩展 CDS 以允许将 JDK 的运行时映像文件 ( $JAVA_HOME/lib/modules) 和应用程序类路径中的存档类加载到内置平台和系统类加载器中。

  • 扩展 CDS 以允许将存档类加载到自定义类加载器中。

非目标

  • 此实现中使用的共享类存档存储格式不会标准化。

  • 在此版本中,CDS 无法归档用户定义模块中的类(例如在 中指定的那些--module-path)。我们计划在未来的版本中添加该支持。

成功指标

如果我们能够实现 (1) 跨多个 JVM 进程的 Java 类元数据使用的内存显着节省空间,以及 (2) 显着缩短启动时间,则该项目将被视为成功。

出于说明目的:

  • 我们可以为包含 6 个 JVM 进程的 Java EE 应用服务器节省大约 340MB 的 RAM,总共消耗 13GB RAM(其中约 2GB 用于类元数据)。

  • 我们可以将 JEdit 基准测试的启动时间缩短 20-30%。

  • 我们可以在 4 个 JVM 进程中将嵌入式 Felix 基准测试的 RAM 使用量减少 18%。

这些数字反映了特定的基准,可能并不普遍适用。这项工作的好处取决于受支持的类加载器加载的类的数量以及整个应用程序的堆使用情况。

描述

JDK 5 中引入的类数据共享允许将一组类预处理到共享存档文件中,然后可以在运行时对其进行内存映射以减少启动时间。当多个 JVM 共享同一存档文件时,它还可以减少动态内存占用。

目前CDS只允许引导类加载器加载归档类。应用程序 CDS(“AppCDS”)扩展了 CDS,以允许内置系统类加载器(也称为“应用程序类加载器”)、内置平台类加载器和自定义类加载器加载存档的类。

对大型企业应用程序的内存使用情况进行分析表明,此类应用程序往往会向应用程序类加载器加载数以万计的类。将 AppCDS 应用于这些应用程序将导致每个 JVM 进程节省数十到数百兆字节的内存。

对无服务器云服务的分析表明,其中许多服务在启动时会加载数千个应用程序类。 AppCDS可以让这些服务快速启动并提高整个系统的响应时间。

启用AppCDS

默认情况下,类数据共享仅针对 JVM 的引导类加载器启用。指定-XX:+UseAppCDS命令行选项以启用系统类加载器(也称为“应用程序类加载器”)、平台类加载器和其他用户定义的类加载器的类数据共享。

确定要归档的类

一个应用程序可能打包有大量的类,但在正常操作期间只使用其中的一小部分。通过仅归档使用的类,我们可以减少文件存储大小和运行时内存使用量。为此,首先使用 正常运行应用程序-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 {#Using--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 进程动态加载的类,但我们发现共享潜力较低且实现更加困难。

相反,我们选择使应用程序类数据共享更加静态:

  • 需要额外的“转储”步骤。

  • 当应用程序的 JAR 文件更新时,需要重复转储步骤。

这是建立在现有 CDS 基础设施之上的,因此实现更简单,我们可以与目标用例实现更高的共享比例。

测试

需要进行广泛的测试来确保兼容性并确认性能优势。

测试应该在所有支持的平台上进行。在某些平台(尤其是 Windows/x86)上,如果 JVM 由于地址空间布局随机化 (ASLR) 无法映射存档,则测试可能会失败。

风险和假设

AppCDS 之前在适用于 JDK 8 和 JDK 9 的 Oracle JDK 中实现。此 JEP 将源代码移至开放存储库,以使该功能普遍可用。由于 AppCDS 已在 JDK 8 和 JDK 9 中进行了广泛测试,因此兼容性和稳定性风险较低。