JDK 9 引入了一种新的编译模式 AOT(Ahead of Time Compilation) 。和 JIT 不同的是,这种编译模式会在程序被执行前就将其编译成机器码,属于静态编译(C、 C++,Rust,Go 等语言就是静态编译)。AOT 避免了 JIT 预热等各方面的开销,可以提高 Java 程序的启动速度,避免预热时间长。并且,AOT 还能减少内存占用和增强 Java 程序的安全性(AOT 编译后的代码不容易被反编译和修改),特别适合云原生场景。
可以看出,AOT 的主要优势在于启动时间、内存占用和打包体积。JIT 的主要优势在于具备更高的极限处理能力,可以降低请求的最大延迟。
AOT 更适合当下的云原生场景,对微服务架构的支持也比较友好。除此之外,AOT 编译无法支持 Java 的一些动态特性,如反射、动态代理、动态加载、JNI(Java Native Interface)等。然而,很多框架和库(如 Spring、CGLIB)都用到了这些特性。如果只使用 AOT 编译,那就没办法使用这些框架和库了,或者说需要针对性地去做适配和优化。举个例子,CGLIB 动态代理使用的是 ASM 技术,而这种技术大致原理是运行时直接在内存中生成并加载修改后的字节码文件也就是 .class
文件,如果全部使用 AOT 提前编译,也就不能使用 ASM 技术了。为了支持类似的动态特性,所以选择使用 JIT 即时编译器。
基于静态编译构建微服务应用
传统的一个 Java 应用从代码编写到启动运行大致可以分为如下步骤:
-
首先,编写 .java 源代码程序。
-
然后,借助 javac 工具将 .java 文件翻译为 .class 的字节码,字节码是 Java 中非常重要的内容之一,正是因为它的出现,Java 才实现对底层环境的屏蔽,达到 Write once, run anywhere 的效果!
-
基于步骤 2 的 .class 文件会被打包成 jar 包或者 war 包进行部署执行,部署过程中通过 Java 虚拟机加载应用程序然后解释字节码运行业务逻辑。
上述过程既给 Java 程序带来了其他编程语言不具备的优势,比如跨平台,易上手等。但同时也给 Java 程序带来了一些性能问题,比如启动速度慢和运行时内存占用高等。
1.冷启动问题
一个 Java 应用启动过程首先需要加载该应用程序对应的 JVM 虚拟机软件程序到内存中,然后 JVM 虚拟机再加载对应的应用程序到内存中,在类加载过程中,应用程序就会开始被解释执行,,解释执行过程 JVM 对垃圾对象进行回收。随着程序的运行的深入,JVM 会采用及时编译(Just In Time,JIT)技术对执行频率较高的代码进行编译优化,以便提升应用程序运行速度。
,一个 Java 程序从启动到达到被JIT动态编译优化会经过 VM init,App init 和 App active 几个阶段,相比于其他一些编译型语言,其冷启动问题比较严重。
2.运行时内存占用高问题
一个 Java 程序运行过程中,什么都不做首先就需要加载一个 JVM 虚拟机,该操作一般占用一定内存。另外,由于 Java 程序是先解释执行字节码,然后再做 JIT 编译优化。
由于相比于一些编译型语言其将编译优化的动作后置到运行时,因此非常容易出现实际加载的代码比实际需要运行的代码多很多的情况,造成了一些无效内存占用情况。