Java虚拟机为已编译为字节码 (但不一定是用Java编写)的应用程序提供了托管运行时环境。 与为特定平台静态编译的代码相比,这为应用程序开发人员提供了许多好处,并且通常可以提高性能。 JVM通过垃圾回收器 (GC)自动处理内存分配和恢复,从而减少了内存泄漏的可能性。 即时(JIT)编译提供了“一次编写,可在任何地方运行”的功能,无需为支持的每个平台构建单独的应用程序二进制版本。
但是,这些优势并非完全没有成本。 尽管在JVM上运行的应用程序的整体速度最终可能会更快,但是由于编译和优化了常用方法,因此需要一定的预热时间。 每次启动应用程序时,都必须执行相同的性能分析,分析和编译,即使该应用程序使用相同。
多年来,Azul Systems一直在研究使JVM这些方面的性能影响最小化的方法。 Zing JVM使用Falcon JIT编译器代替了旧的C2 JIT和ReadyNow! 记录可在重新启动应用程序时使用的配置文件的技术。
Azul的OpenJDK的Zulu构建现在包括一组类似的技术,我们称之为Main(CRaM)的Checkpoint / Restore。
CRaM的想法是通过执行训练运行来减少应用程序的预热时间,然后可以在生产运行期间使用它。 可以通过三种不同的方式执行训练运行:
- 正常使用该应用程序,并执行所需的任何功能。 该应用程序通过退出main()方法终止。 此时,将记录来自应用程序运行的所有数据。 无需更改应用程序代码; 仅需添加-Zcheckpoint JVM标志。
- 根据上面的方案1,使用该应用程序,但通过调用System.exit()终止该应用程序。 同样,无需更改应用程序代码,但是在这种情况下,必须使用JVM标志-Dcom.azul.System.exit.doCheckpointRestore = true。
- 在这种情况下,开发人员在应用程序代码中选择一个特定点,他们希望在此生成检查点。 必须更改应用程序代码; 在需要的地方放置了对方法Dcom.azul.System.tryCheckpointRestore()的调用。 这对于不终止的应用程序很有用。 除非为JVM指定了-Zcheckpoint标志,否则该调用将被忽略。 附加标志-XX:CRTrainingCount可用于使应用程序在记录检查点之前处理多个事务。
检查点是创建应用程序状态时的复杂快照。 它包含以下信息:
- JVM的Java类的内部表示。 每次应用程序启动时,它都需要读取所需的类,并使用初始化的数据为每个类创建自己的表示形式。
- JVM JIT编译器C1和C2生成的代码。 由于该代码的重用方式,有必要关闭某些优化以使该代码在生产运行中得以重用。
- 初始化的系统类。 这些是核心类库中的类,并且独立于任何应用程序代码。
- 堆中与应用程序启动相关的某些Java对象。
对于可以在生产运行中使用检查点的地方,存在严格的限制。 检查点与用于训练运行的平台紧密相关,并且包括非常低级的信息,例如来自映射系统库(如libc)的内存页。 如果在执行生产运行之前对系统库,JDK或应用程序代码进行了更改,则检查点将不起作用。 检查点只能在运行相同硬件和软件堆栈的计算机之间共享。
要将检查点用于生产运行,应使用如下命令行:
java -Zrestore myAppClass <application arguments>
先前存储的检查点数据将用于最大程度地减少与应用程序关联的预热时间。 有两点需要注意:
- 在生产运行期间,可以将代码重新编译为JIT编译过程的正常部分。 与培训期间不同,将启用JIT可用的所有优化。
- 需要从生成训练运行的目录中启动该应用程序。 这是检查点状态的一部分。
- 不应使用JVM命令行标志。 训练运行与创建期间使用的命令行标志相关联,然后在生产运行期间自动设置这些标志。 更改它们可能会使检查点中的信息无效。
当前,CRaM功能面向嵌入式应用程序,在这些应用程序中,启动时以最佳速度运行的能力至关重要。 因此,CRaM支持的平台仅是Arm 32位处理器,运行Linux的内核为3.5或更高版本以及glibc 2.13或更高版本。 CRaM包含一个实用程序cr-compat-checker,可用于验证设备是否满足这些要求。
为了确定CRaM是否适合某个应用程序,了解它如何更改应用程序的性能概况至关重要。 CRaM旨在减少到达生成检查点的时间。 从那时起,无论是否使用检查点,执行都将保持不变。 查看Java应用程序的性能时,它可以分为两部分:JVM启动时间,即到达main()入口点的时间; 和时间从main()运行。 使用CRaM时,到达main()所需的时间会更长,但是到达创建检查点的位置所需的时间会更少。
为了使这一点更易于理解,图表非常有用:
例如,考虑一个简单的Spring Boot应用程序。
在不使用CRaM的情况下,到main()的时间为2秒,从进入main()到完全初始化的应用程序(准备处理交易)的时间为31秒。 因此,处理交易之前需要的时间为33秒。
完成检查点后,使用CRaM启动应用程序,到main()的时间增加到3秒。 但是,从输入main()到完全初始化的时间仅为18秒。 这样可以将处理事务之前所需的时间减少到仅21秒,这实际上要快得多。
如您所见,CRaM可以在需要准备好尽快执行任务的应用程序的有效性方面产生重大影响。 这在嵌入式应用程序中尤其重要,在嵌入式应用程序中,资源受到限制,并且与传统服务器相比,设备可能需要更频繁地重新启动。
Azul目前正在进行CRaM的Beta版测试。 如果您有兴趣参与其中,请与我们联系以获取更多信息。
翻译自: https://www.javacodegeeks.com/2019/08/faster-java-startup-checkpoint-restore-main.html