0.前言
0.1.Java程序执行过程
0.2.编译的快与慢
0.2.1.Java虚拟机(解释的(Interpreted)相对慢)

0.2.2.即时编译器(Just In Time Compiler)(相对快)

0.2.3.Java编译器(Java Compiler)(相对快)
Eg: Graal编译器(既可以作为AOT编译器,又可以在JIT编译器中替换C2)、AOT(jaotc)
1.JIT编译过程
- Java应用启动后,在完成类加载、字节码验证后,并不会马上触发JIT编译器进行编译,而是先由即时解释器进行解释和分析;
- 即时解释器在完成了初步的解释和分析后,JIT编译器会利用已经收集到的分析信息来查找热点(经常执行的代码部分),有了热点代码后,C1就可以对这些热点代码进行分析,基于分析的结果编译生成相对高效的目标机器代码,从而使得此时的Java虚拟机具有本机的代码性能,于此同时C1也会进行进一步的分析;
- C1在完成了进一步的分析后,C2就会利用C1产生的分析信息,进行更积极、更耗时的优化,此时C2会重新编译代码,以生成更高效的目标机器代码,从而更明显的提升Java虚拟机的代码性能。
- 综上所述,基于热点的更多信息,C1 性能提升更快,而 C2 性能提升更好。
2.当前需求
Azul可以在模拟环境下预热(模拟出热点方法和循环体),然后将结果注入生产环境,直接使用 C2 编译,减少运行时的编译时间。这对于证券行业报盘等场景很有效,因为这些场景一开始就需要高速运转。
- 横轴:JVM虚拟机达到最佳代码性能的时间;
- 纵轴:JVM最佳代码性能程度或比率;
- 毛刺:去优化和垃圾回收导致的。
3.问题
Azul可以在模拟环境中学习和训练,然后将训练结果集作为参考输入到生产环境中,以达到启动时的峰值效果。 并消除 GC毛刺。
3.1.当前问题
- 启动时间长;
- 需要很长时间Java虚拟机才可以达到Java虚拟机的最佳代码性能。
3.2.期望结果
- 启动时间短;
- 启动后Java虚拟机可快速达到最佳代码性能。
3.3.期望目标
- 在保证关键功能正确使用的前提下,显著降低再次启动耗时;
- 消除毛刺,使得Java虚拟机快速达到最佳代码性能。
4.问题分析
4.1.现在加速启动的方案有哪些?
4.1.1.CDS(Class Data Sharing,类数据共享)
功能定位: 将内部类、应用类、动态(自定义类加载器加载的类和省去dump classlist步骤)等表示转储到文件中(类加载器、jsa文件); 在每个 JVM 启动时共享 (CDS)。
不足: 没有优化或热点检测; 仅减少类加载时间; 启动速度加快不明显。
4.1.2.AOT(Ahead Of Time Compilation,提前编译,源码到机器码)
优点: 从一开始就“全速”,GraalVM 原生镜像可以做到这一点; 根据定义,AOT 是静态的,代码在运行之前进行编译,运行时编译代码没有开销; 内存占用小
不足: 不解释字节码; 没有热点分析; 没有代码的运行时编译,所以没有运行时字节码生成; 方法内联的有限使用; 反射是可能的,但很复杂; 无法使用推测性优化 (假设条件成立,如数组边界) 必须针对共同特征(如同名同参数等)进行编译 由于优化不彻底,所以总体性能通常会较低; 部署环境!=开发环境。
4.1.3.JIT(Just In Time Compilation,即时编译)
优点: 可以在运行时使用激进的方法内联(如方法行数不超过80行,激进指大方法,编译时间长,产生大的二进制文件) 可以使用运行时字节码生成 反射很简单 可以使用推测性优化?(假设条件成立,如数组边界) 甚至可以针对 Haswell、Skylake、Ice Lake 等进行优化。(CPU架构) 总体性能通常会更高 部署环境 == 开发环境
不足: 需要更多时间来启动(但会更快) 在运行时编译代码有开销 更大的内存占用
4.2.为什么会产生毛刺(毛刺的存在直接影响Java虚拟机达到最佳性能)?
4.2.1.存在去优化
尽管 C2 编译的代码经过高度优化且寿命较长,但它也可能会被取消优化。结果,JVM 将暂时回滚到解释状态。 当编译器的乐观假设被证明是错误的时,就会发生去优化 - 例如,当配置文件信息与方法行为不匹配时,一旦热路径发生变化,JVM 就会取消优化已编译和内联的代码。
4.2.2.达到最佳性能前有GC操作
5.解决方案
5.1.(商用收费)Azul Prime ReadyNow
5.1.1.什么是Azul Prime ReadyNow?
ReadyNow 是 Azul Platform Prime 的一项功能,启用后可显著改善应用程序的预热行为。
5.1.2.什么是预热?
预热是指Java应用程序达到最佳性能所需的时间。实时(JIT)编译器的任务是通过从应用程序字节码生成优化的编译代码来提供最佳性能。这个过程需要一些时间,因为JIT编译器会根据应用程序的分析来寻找优化机会。
5.1.3.基本思路
ReadyNow 会保留应用程序运行期间收集的分析信息,以便后续运行不必再次从头开始学习。预热可以改善每个应用程序的运行,直到达到最佳性能。
5.1.4.使用方法
要启用 ReadyNow,请添加以下命令行选项,其中 通常对于两者来说是相同的: ● -XX:ProfileLogIn= 指示 Azul Platform Prime 使用现有配置文件日志中的信息。 ● -XX:ProfileLogOut= 记录先前的编译和运行中的去优化决策。 运行应用程序将自动生成或更新配置文件日志。此配置文件日志将在应用程序的后续运行时使用,从而改善预热。
5.1.5.集成开发
闭源
5.2.(开源免费)CRaC
5.2.1.什么是CRaC?
Referenced to CRIU(Checkpoint Restore In Userspace,在用户空间的检查点恢复) CRaC(Coordinated Restore at Checkpoint,检查点协调恢复)项目研究 Java 程序与在执行时对 Java 实例进行检查点(制作镜像、快照)的机制的协调。 从映像恢复可以解决启动和预热时间方面的一些问题。 该项目的主要目标是开发一种新的与机制无关的标准 API,以通知 Java 程序有关检查点和恢复事件的信息。 其他研究活动将包括但不限于与现有检查点/恢复机制的集成以及新机制的开发、对 JVM 和 JDK 的更改以缩小镜像并确保它们是正确的。
5.2.2.基本思路
向运行在特定(金丝雀)环境(CPU、内存、I/O、操作系统等)下的Java应用进行数据输入(模拟请求),当输入的数据达到饱和状态后(请求的路径覆盖了所有的用例),冻结正在运行的应用,将Java虚拟机达到最佳性能的检查点输出为快照文件进行留存,后续就可以通过先前保存的镜像文件启动应用(理论上可以是不同的物理机器)。
5.2.3.部署流程
向运行在金丝雀环境的应用发起模拟请求,达到饱和请求后生成快照文件,然后在生产环境通过快照文件进行恢复应用。
5.2.4.使用方法
要启用 CRaC,请添加以下命令行选项,其中 通常对于两者来说是相同的: ● -XX:CRaCRestoreFrom=cr 指示JDK使用现有配置文件日志中的信息。 ● -XX:CRaCCheckpointTo=cr 记录先前的编译和运行中的去优化决策。
5.2.5.集成开发
5.2.5.1.获取源码
git clone https://github.com/openjdk/crac.git -b {tag}
5.2.5.2.手动编译
bash configure
make images
mv build/linux-x86_64-server-release/images/jdk/ .
5.2.5.3.下载兼容后的CRIU
5.2.5.3.在JDK中的同一个命名文件上提取并复制criu二进制文件
cp criu-dist/sbin/criu jdk/lib/criu
5.2.5.4.执行授权
sudo chown root:root jdk/lib/criu
sudo chmod u+s jdk/lib/criu
6.验证
6.1.官方验证
6.1.1.验证环境
笔记本电脑(Intel i7-5500U, 16Gb RAM and SSD.) 操作系统内核(Linux kernel 5.7.4-arch1-1) 操作系统(ubuntu:18.04 based image) 平台: archlinux
6.1.2.验证场景
6.1.2.1.场景一:Time To First Operation
6.1.2.2.场景二:Time to Complete N operations:sprint-boot(OpenJDK ON/OFF CRaC)
6.1.2.3.场景三:Time to Complete N operations:quarkus(OpenJDK ON/OFF CRaC)
6.1.2.4.场景四:Time to Complete N operations:micronaut(OpenJDK ON/OFF CRaC)
6.2.本地验证
6.2.1.验证环境
笔记本(3.1 GHz Intel Core i5, 4Gb RAM and 50Gb SSD.) 操作系统内核(3.10.0-1160.102.1.el7.x86_64) 操作系统(CentOS:7.9 VM) 平台: x86_64 VM Parameters:除存放和加载镜像日志的参数外,没使用其它参数