我们都知道,Java中有两种编译方法:
1、javac把java代码编译成字节码,然后由Java虚拟机解释执行。
2、JIT把java代码直接编译成机器码,然后由Java虚拟机直接运行。
但是,JIT编译有一些比较明显的缺点也是不能忽视的:
而在如今的云原生盛行的今天,应用的快速启动以及减少预热时长是非常重要的,其实是Serverless场景中,所以,一个新兴的编译器GraalVM就诞生了。他提出了一种新的编译方式——Ahead of Time,及AOT编译。
AOT编译,翻译一下就是提前编译,它不像JIT一样在运行期才生成机器码,而是在编译期间就将字节码转换为机器码,这就直接省去了运行时对JVM的依赖。这是一种典型的静态编译技术。
java 静态编译,是指将java程序的字节码在单独的离线阶段编译为汇编代码,它的输入是Java的字节码,输出是native image。
这里多说一句,很多人会误以为编译期的编译不是javac吗?其实javac只是把源代码(.java)编译成中间代码(.class),这种中间代码我们叫做字节码,而字节码并不是机器代码,无法直接执行,需要解释器进行解释执行。详见:
因为AOT编译是在编译期就生成机器代码了,所以,应用启动时就不需要编译,那么他就可以大大的减少应用的启动时间,提升系统的整体性能。所以他非常适用于对启动时间敏感的场景,例如云原生应用。
与传统的Java的运行时编译(动态编译/jit)相比,静态编译主要有几个重要的优势:
1、机器执行的时候执行的是经过编译优化的本地代码。执行本地代码可以非常的高效和快速,并不需要进行解释执行和JIT编译,就可以直接执行。
2、静态编译后的可执行程序自包含了轻量级运行时支持,所以他在运行时不再需要依赖额外的JVM。
3、解决应用程序的冷启动问题。有了静态编译后的本地代码,应用程序就可以快速地启动,不再解释执行,也不再需要JIT的预热。
4、打破Java程序与本地代码之间的边界。因为编译后的代码也是本地代码,所以JNI调用的开销更低了。
一个Java程序之所以能做静态编译,需要满足一个至关重要的前提——封闭性假设。
什么是封闭性假设,也就是说他要求所有运行时的内容必须在编译时可见,并且可以被编译到native image中。但是Java中有很多代码是没有办法在编译期就确定的,比如反射,他就是不满足封闭性假设的。
其他的类似的违反封闭性假设的特性还有动态代理、序列化、JNI、动态类加载等等。所以,遇到这些代码的时候,就需要额外的适配来解决。带来了很多的复杂性,也给静态编译带来了一定的局限性。
Java有一个很重要的特性就是平台相关性,但是随着静态编译的盛行,这个说法已经并不一定就成立了。
静态编译以后的代码程序,其实就是平台相关的了。