- 为什么 Java 要在虚拟机里运行?
- Java 虚拟机具体是怎样运行 Java 字节码的?
- Java 虚拟机的运行效率究竟是怎么样的?
为什么 Java 要在虚拟机里运行?
- Java是一种高级语言,不能直接运行在硬件,需要转换
- 设计一个面向Java语言的虚拟机,通过编译器将 Java 程序转换成该虚拟机所能识别的指令序列。
- 在现有平台(Windows Linux)上实现Java虚拟机,Java程序便可以在不同平台上的虚拟机实现里运行。
Java 虚拟机具体是怎样运行 Java 字节码的?
- 从虚拟机视角来看
- 执行 Java 代码首先需要将它编译而成的 class 文件加载到 Java 虚拟机中。加载后的 Java 类会被存放于方法区(Method Area)中。实际运行时,虚拟机会执行方法区内的代码。
- 在运行过程中,每当调用进入一个 Java 方法,Java 虚拟机会在当前线程的 Java 方法栈中生成一个栈帧,用以存放局部变量以及字节码的操作数。这个栈帧的大小是提前计算好的,而且 Java 虚拟机不要求栈帧在内存空间里连续分布。
- 当退出当前执行的方法时,不管是正常返回还是异常返回,Java 虚拟机均会弹出当前线程的当前栈帧,并将之舍弃。
- 从硬件视角来看,Java 字节码无法直接执行。因此,Java 虚拟机需要将字节码翻译成机器码。
- 第一种是解释执行,即逐条将字节码翻译成机器码并执行;
- 第二种是即时编译(Just-In-Time compilation,JIT),即将一个方法中包含的所有字节码编译成机器码后再执行。
Java 虚拟机的运行效率究竟是怎么样的?
- 即时编译建立在程序符合二八定律的假设上,也就是百分之二十的代码占据了百分之八十的计算资源。
- 即时编译,对于占据大部分的不常用的代码,我们无需耗费时间将其编译成机器码,而是采取解释执行的方式运行;
- 另一方面,对于仅占据小部分的热点代码,我们则可以将其编译成机器码,以达到理想的运行速度。
为了满足不同用户场景的需要,HotSpot 内置了多个即时编译器:
- C1 又叫做 Client 编译器,面向的是对启动性能有要求的客户端 GUI 程序,采用的优化手段相对简单,因此编译时间较短。
- C2 又叫做 Server 编译器,面向的是对峰值性能有要求的服务器端程序,采用的优化手段相对复杂,因此编译时间较长,但同时生成代码的执行效率较高。
- Graal 是 Java 10 正式引入的实验性即时编译器。
从 Java 7 开始,HotSpot 默认采用分层编译的方式:热点方法首先会被 C1 编译,而后热点方法中的热点会进一步被 C2 编译。
总结与实践
今天我简单介绍了 Java 代码为何在虚拟机中运行,以及如何在虚拟机中运行。
之所以要在虚拟机中运行,是因为它提供了可移植性。一旦 Java 代码被编译为 Java 字节码,便可以在不同平台上的 Java 虚拟机实现上运行。此外,虚拟机还提供了一个代码托管的环境,代替我们处理部分冗长而且容易出错的事务,例如内存管理。
Java 虚拟机将运行时内存区域划分为五个部分,分别为方法区、堆、PC 寄存器、Java 方法栈和本地方法栈。Java 程序编译而成的 class 文件,需要先加载至方法区中,方能在 Java 虚拟机中运行。
为了提高运行效率,标准 JDK 中的 HotSpot 虚拟机采用的是一种混合执行的策略。它会解释执行 Java 字节码,然后会将其中反复执行的热点代码,以方法为单位进行即时编译,翻译成机器码后直接运行在底层硬件之上。
HotSpot 装载了多个不同的即时编译器,以便在编译时间和生成代码的执行效率之间做取舍。