深入拆解Java虚拟机学习笔记1

为什么Java需要在Java虚拟机里面运行?

Java 作为一门高级程序语言,它的语法非常复杂,抽象程度也很高。因此,想要像汇编语言一样直接在硬件上运行这种复杂的程序并不现实。所以呢,在运行 Java 程序之前,我们需要对其进行一番转换,转换成为机器可以理解的语言。当前主流的转换思路是这样子的,设计一个面向 Java 语言特性的虚拟机,并通过编译器将 Java 程序转换成该虚拟机所能识别的指令序列,也称 Java 字节码。

Java 虚拟机可以由硬件实现,但更为常见的是在各个现有平台(如 Windows_x64、Linux_aarch64)上提供软件实现。这么做的意义在于,一旦一个程序被转换成 Java 字节码,那么它便可以在不同平台上的虚拟机实现里运行。这也就是我们经常说的“一次编写,到处运行”。
虚拟机的另外一个好处是它带来了一个托管环境(Managed Runtime)。这个托管环境能够代替我们处理一些代码中冗长而且容易出错的部分。其中最广为人知的当属自动内存管理垃圾回收机制。除此之外,托管环境还提供了诸如数组越界动态类型安全权限等等的动态检测,使我们免于书写这些无关业务逻辑的代码,能够更加专注与业务

Java 虚拟机具体是怎样运行 Java 字节码的

从上面我们已经知道了执行 Java 代码首先需要将它编译而成的 class 文件加载到 Java 虚拟机中。加载后的 Java 类会被存放于方法区(Method Area)中。在实际运行的时候,虚拟机会执行方法区内的代码。这种机制和X86的段式内存管理中类似。

JVM同样也在内存中划分出堆和栈来存储运行时数据。不仅如此JVM会将栈细分为面向 Java 方法的方法栈,面向本地方法(用 C++ 写的 native 方法)的本地方法栈,以及存放各个线程执行位置的 PC 寄存器。其中方法区和堆区是线程共有的,而栈区、PC寄存器、本地方法栈线程私有的。

Java深入虚拟机第一章.png

在程序运行的过程中,每调用一个 Java 方法,JVM就会在当前线程的方法栈中生成一个栈帧,用以存放局部变量以及字节码的操作数。这个栈帧的大小是提前计算好的,而且栈桢可以不需要再内存中连续分布。当程序退出当前执行的方法时,不管是正常返回还是异常返回,JVM均会舍弃当前线程的当前栈帧。另外因为字节码无法被硬件直接执行。因此,JVM需要将字节码翻译成机器码。
在HotSpot里面,上述翻译过程有两种形式:第一种是解释执行,即逐条将字节码翻译成机器码并执行;第二种是即时编译(Just-In-Time compilation,JIT),即将一个方法中包含的所有字节码编译成机器码后再执行。

Java深入虚拟机第一章-两种翻译流程.png

解释执行的优势在于无需等待编译,而即时编译的优势在于实际运行速度更快

HotSpot默认采用混合模式,综合了解释执行和即时编译两者的优点。它会先解释执行字节码,而后将其中反复执行的热点代码,以方法为单位进行即时编译。

1-1总结与实践

  • 本节介绍了 Java 代码为何在虚拟机中运行,以及如何在虚拟机中运行。
  • 之所以要在虚拟机中运行,是因为它提供了可移植性。一旦 Java 代码被编译为 Java 字节码,便可以在不同平台上的 Java 虚拟机实现上运行。
  • Java虚拟机还提供了一个代码托管的环境,代替我们处理部分冗长而且容易出错的事务,例如内存管理。
  • Java 虚拟机将运行时内存区域划分为五个部分,分别为方法区、堆、PC 寄存器、Java 方法栈和本地方法栈。Java 程序编译而成的 class 文件,需要先加载至方法区中,方能在 Java 虚拟机中运行。
  • 为了提高运行效率,标准 JDK 中的 HotSpot 虚拟机采用的是一种混合执行的策略。它会解释执行 Java 字节码,然后会将其中反复执行的热点代码,以方法为单位进行即时编译,翻译成机器码后直接运行在底层硬件之上。
  • HotSpot 装载了多个不同的即时编译器,以便在编译时间和生成代码的执行效率之间做取舍。