1. JDK、JRE、JVM

JDK是一个功能齐全的Java开发工具,其中包括JRE与javac等工具。

JRE是运行已编译Java程序的环境,其中包括JVM与Java基础类库。

JVM是运行Java字节码的虚拟机。

但Java9后不再区分JDK与JRE,JDK被拆分为94个模块+Jlink工具,Java应用可以通过Jlink工具创建出仅包含所依赖的JDK模块的自定义运行镜像,极大减小运行时环境的大小。

2. 解释与编译并存

高级语言按照程序的执行方式分为两种:

编译型:通过编译器一次性将源代码编译为机器码后执行,执行速度快,开发效率慢。

解释型:通过解释器逐句将源代码解释为机器码执行,执行速度慢,开发效率快。

而Java语言先编译(源代码编译为.class文件)再解释(字节码解释为机器码),同时也有JIT技术即时编译,所以是“解释与编译并存”。

3. Java程序从源代码到运行

.java程序通过javac工具编译成为.class文件,class文件通过解释器生成机器码运行。

.class文件即为字节码。

为什么需要字节码?Java通过字节码一定程度上解决了传统解释型语言运行效率低的问题,同时保留了解释型语言可移植的特性(class文件不针对具体操作系统,可以实现Write Once,Run Anywhere)。

javac将源码编译为字节码的过程中,会发生前端编译,即对代码进行词法分析、语法分析、语义分析。

接下来解释器会对.class文件逐条解释执行并搜集程序运行时的信息,基于这些信息,对JVM认定的热点代码进行后端编译(将字节码编译为机器码)。即JVM设置阈值,如果方法或代码块的调用次数超过阈值则认定为热点代码,编译为机器码存入CodeCache中,下一次执行到该段代码时,就会直接在codeCache中读取,可以大大提高程序运行的性能。这也就是JIT(Just In Time)即时编译的过程。

基本功 | Java即时编译器原理解析及实践 - 美团技术团队

那么有没有办法可以让Java像C/C++一样,在执行前就编译好呢?这就是AOT(Ahead of Time)提前编译,或者说静态编译。这种技术可以避免JIT的冷启动问题(即启动速度慢)与运行时内存占用高的问题,其编译后的代码不容易被反编译和修改,安全性能高,而且其镜像体积非常小(只包含代码和必要的运行时组件),易于分发。集合以上优点,AOT特别适用于云原生场景。

JIT为什么启动速度慢,运行时内存占用高?(基于静态编译构建微服务应用

一个 Java 应用启动过程首先需要加载该应用程序对应的 JVM 虚拟机软件程序到内存中。然后 JVM 虚拟机再加载对应的应用程序到内存中。在类加载过程中,应用程序就会开始被解释执行。解释执行过程 JVM 对垃圾对象进行回收。随着程序的运行的深入,JVM 会采用及时编译(Just In Time,JIT)技术对执行频率较高的代码进行编译优化,以便提升应用程序运行速度。从启动到达到被JIT动态编译优化会经过VM init,App init 和 App active 几个阶段,相比于其他一些编译型语言,其冷启动问题比较严重。

一个 Java 程序运行过程中,什么都不做首先就需要加载一个 JVM 虚拟机,该操作一般占用一定内存。另外,由于 Java 程序是先解释执行字节码,然后再做 JIT 编译优化。由于相比于一些编译型语言其将编译优化的动作后置到运行时,因此非常容易出现实际加载的代码比实际需要运行的代码多很多的情况,造成了一些无效内存占用情况。综上所述就是为什么很多人常诟病 Java 程序运行内存占用高的几点主要原因。

当然JIT也有他的优点,具备更高的极限处理能力,可以减少请求的最大延迟。

既然AOT有这么多优点,为什么不全用AOT?除了上述JIT的优点外,AOT还有一些问题:不适用于Java中如反射、动态代理、动态加载等动态特性,需要针对性的做适配。