很多云上的新应用不约而同地选择了 Go 语言,很大的原因是 Go 应用对运行时没有依赖,静态编译的程序启动速度快,也不需要通过 JIT 来预热。
Java 静态编译技术是一种激进的 AOT 技术,通过单独的编译阶段将 Java 程序编译为本地代码,在运行时无需传统 Java 虚拟机和运行时环境,只需操作系统类库支持即可。
静态编译技术实现了 Java 语言与原生 native 程序的“合体”,将原本的 Java 程序编译成为了一个自举的具有 Java 行为的原生 native 程序,由此兼有 Java 程序和原生 native 程序的优点。
Java编译原理:前端编译、即时编译(JIT编译)、静态提前编译(AOT编译)
1、前端编译
把Java源码文件(.java)编译成Class文件(.class)的过程;
也即把满足Java语言规范的程序转化为满足JVM规范所要求格式的功能;
优点:
*这阶段的优化是指程序编码方面的;
*许多Java语法新特性(“语法糖”:泛型、内部类等等),是靠前端编译器实现的,而不是依赖虚拟机;
*编译成的Class文件可以直接给JVM解释器解释执行,省去编译时间,加快启动速度;
缺点:
- 对代码运行效率几乎没有任何优化措施;
- 解释执行效率较低,所以需要结合下面的JIT编译;
- 前端编译器:Oracle javac、Eclipse JDT中的增量式编译器(ECJ)等;
2、后端编译/即时JIT编译
通过Java虚拟机(JVM)内置的即时编译器(Just In Time Compiler,JIT编译器);在运行时把Class文件字节码编译成本地机器码的过程;
优点:
- 通过在运行时收集监控信息,把"热点代码"(Hot Spot Code)编译成与本地平台相关的机器码,并进行各种层次的优化;
- 可以大大提高执行效率;
缺点:
- 收集监控信息影响程序运行;
- 编译过程占用程序运行时间(如使得启动速度变慢);
- 编译机器码占用内存;
JIT编译器:HotSpot虚拟机的C1、C2编译器等;
另外,JIT编译速度及编译结果的优劣,是衡量一个JVM性能的很重要指标;
所以对程序运行性能优化集中到这个阶段;
也就是说可以对这个阶段进行JVM调优;
3、静态提前编译/AOT编译
程序运行前,直接把Java源码文件(.java)编译成本地机器码的过程;
优点:
- 编译不占用运行时间,可以做一些较耗时的优化,并可加快程序启动;
- 把编译的本地机器码保存磁盘,不占用内存,并可多次使用;
缺点:
- 因为Java语言的动态性(如反射)带来了额外的复杂性,影响了静态编译代码的质量;
- 一般静态编译不如JIT编译的质量,这种方式用得比较少;
静态提前编译器(AOT编译器):JAOTC、GCJ、Excelsior JET、ART (Android Runtime)等;
关于ART (Android Runtime)模式:ART虽然主要通过AOT编译支持Java的运行,但仍然带有解释器
4、前端编译+JIT编译
目前Java体系中主要还是采用前端编译+JIT编译的方式,如JDK中的HotSpot虚拟机。
前端编译+JIT编译方式的运作过程大体如下:
1、首先通过前端编译把符合Java语言规范的程序代码转化为满足JVM规范所要求Class格式;
2、然后程序启动时Class格式文件发挥作用,解释执行,省去编译时间,加快启动速度;
3、针对Class解释执行效率低的问题,在运行中收集性能监控信息,得知"热点代码";
4、JIT逐渐发挥作用,把越来越多的热点代码"编译优化成本地代码,提高执行效率;
5、JIT和AOT对比
JIT:吞吐量高,有运行时性能加成,可以跑得更快,并可以做到动态生成代码等,但是相对启动速度较慢,并需要一定时间和调用频率才能触发 JIT 的分层机制
AOT:内存占用低,启动速度快,可以无需 runtime 运行,直接将 runtime 静态链接至最终的程序中,但是无运行时性能加成,不能根据程序运行情况做进一步的优化
JDK9的AOT
Java 9 引入了 aot 编译方式,能够将 class 文件直接编译成可执行二进制文件。
目前 JDK AOT 的局限有:
- 仅支持 64 位 Linux 操作系统:;
- 操作系统需要预装 libelf 库,以确保能够生成 elf 文件:
- AOT 编译和执行环境需要相同:
- Java 9 最初发布时,只支持 java.base 模块可以编译成 AOT 库;
- 目前只支持 G1 和 Parallel GC 两种 GC 方式:前面没有提到,AOT 编译时的 JVM 参数和运行时需要相同,也包括 GC 方式,也就是说如果用了 AOT,JVM 实际运行时也只能使用这两种 GC 方式之一;
- 可能会无法编译通过动态生成 class 文件或者修改字节码的 java 代码(如 lambda 表达式、反射调用等)
- JVM 运行时参数设置必须和 AOT 库编译时相同;
AOT 可能带来的好处,是 JVM 加载这些已经预编译成二进制库之后,可以直接调用,而无需再将其运行时编译成二进制码。理论上,AOT 的方式,可以减少 JIT 带来的预热时间,减少 Java 应用长期给人带来的“第一次运行慢”感觉。
AJDK的AOT
JVM 团队与 SOFAStack 团队密切合作,在中间件应用上率先实现静态编译的落地。将一个应用的启动速度从 60 秒优化到 3.8 秒,双十一期间静态编译的应用运行稳定,没有故障, GC 停顿时间在 100 毫秒,在业务允许范围之内,内存占用和 RT 与传统 Java 应用持平。
综上所述,静态编译的应用在稳定性、资源占用、RT 响应等各方面指标与传统 Java 应用基本持平的状况下,将启动时间降低了 2000% 。