JVM 虚拟机详解

151 阅读6分钟

本文介绍了 Java 虚拟机 (JVM) 及其体系结构,JVM 代表 Java 虚拟机。它为您提供了执行已编译程序(称为字节码)的环境。不同供应商针对各种平台提供了多种 JVM 实现。在本文中,我将解释 JVM 的主要组件,包括内存管理、类加载和垃圾收集器。

通常我们不会深入研究 JVM 的内部机制。如果我们的代码可以运行,那么我们就不会太关心内部机制……直到有一天出现问题,我们需要调整 JVM 或修复内存泄漏。

Java 虚拟机问题在工作面试中非常常见。面试官喜欢问有关 JVM 的各种问题,以证明您对 Java 平台的一般了解。

什么是Java虚拟机

Java 的设计理念是“一次编写,随处运行”,可在各种平台上运行。这是什么意思呢?与 C++ 等编程语言不同,C++ 的代码是针对特定平台编译并在该平台上本地运行的,而 Java 源代码首先被编译为字节码 -  .class文件。编译后,类文件由虚拟机 (VM) 解释。请看下面的图表

image.png

在不同平台上运行 Java 字节码——“一次编写,随处运行”的概念

首先,我们将 Java 源代码(.java 文件)编译为字节码(.class 文件)。字节码是 Java 和机器语言之间的中间语言。您可以在任何 JVM 实现上执行相同的字节码,而无需针对某个操作系统或平台调整代码。

Java虚拟机体系结构

Java虚拟机由三个主要区域组成:

  • 类加载器子系统
  • 运行时数据区
  • 执行引擎 我们将更详细地讨论每一个 image.png

Java虚拟机架构图

类加载器子系统

我们已经在单独的教程中介绍了类加载器。可以查看Java 类加载器详解以了解更多详细信息。

加载中

编译后的类存储为 .class 文件。当我们尝试使用某个类时,Java ClassLoader会将该类加载到内存中。当某个类在已运行的类中按名称引用时,该类将被引入 Java 环境。一旦第一个类开始运行,类加载器便会尝试加载类。运行第一个类通常是通过声明和使用静态 main() 方法来完成的。

类加载器有三种类型:

  1. Bootstrap 类加载器 - 它加载 JDK 内部类,通常加载 rt.jar 和其他核心类,例如 java.lang.* 包类
  2. 扩展类加载器 ——它从 JDK 扩展目录(通常是 JRE 的 lib/ext 目录)加载类。
  3. 系统类加载器 ——从系统类路径加载类,可以在调用程序时使用 -cp 或 -classpath 命令行选项进行设置。

链接

链接一个类或接口涉及验证和准备该类或接口、它的直接超类、它的直接超接口以及它的元素类型(如果需要)。

JVM 要求维护以下所有属性:

  • 类或接口在链接之前已经完全加载。
  • 类或接口在初始化之前已经过完全验证和准备。
  • 链接期间检测到的错误会被抛在程序中的某个位置,在该位置程序采取某些操作可能直接或间接地需要链接到错误所涉及的类或接口。

初始化

类或接口的初始化包括执行其类或接口初始化方法或调用类的构造函数等。

由于 Java 虚拟机是多线程的,因此类或接口的初始化需要仔细同步,因为其他线程可能正尝试同时初始化同一个类或接口。 这是类加载的最后阶段,此处所有静态变量都将被赋予原始值,并且静态块将被执行。

运行时数据区

运行时数据区内有五个组件:

方法区

所有类级别的数据(包括静态变量)都将存储在这里。每个 JVM 只有一个方法区,并且它是一个共享资源。

堆区域

所有对象及其对应的实例变量和数组都将存储在此处。每个 JVM 也有一个堆区域。由于方法区域和堆区域共享多个线程的内存,因此存储的数据不是线程安全的。

堆栈区域

对于每个线程,都会创建一个单独的运行时堆栈。对于每个方法调用,都会在堆栈内存中创建一个条目,称为堆栈框架。所有局部变量都将在堆栈内存中创建。堆栈区域是线程安全的,因为它不是共享资源。堆栈框架分为三个子实体:

  • 局部变量数组——与方法相关,涉及多少个局部变量以及相应的值将存储在这里。
  • 操作数堆栈——如果需要执行任何中间操作,操作数堆栈将充当运行时工作区来执行该操作。
  • 帧数据 – 与方法对应的所有符号都存储在这里。如果发生任何异常,捕获块信息将保留在帧数据中。

PC 寄存器

每个线程都有单独的 PC 寄存器,用于保存当前执行指令的地址,一旦执行该指令,PC 寄存器将使用下一条指令进行更新。

本机方法堆栈

本地方法栈保存本地方法信息。每个线程都会创建一个单独的本地方法栈。

执行引擎

分配给运行时数据区的字节码将由执行引擎执行。执行引擎读取字节码并逐段执行。

解释器

解释器对字节码的解释速度比较快,但是执行速度比较慢,解释器的缺点是当一个方法被多次调用时,每次都需要重新解释一次。

JIT 编译器

JIT 编译器弥补了解释器的缺点。执行引擎将使用解释器的帮助来转换字节码,但当它发现重复的代码时,它会使用 JIT 编译器,它会编译整个字节码并将其更改为本机代码。此本机代码将直接用于重复的方法调用,从而提高系统的性能。

垃圾收集器

垃圾收集器 (GC) 收集并删除未引用的对象。可以通过调用“System.gc()”触发垃圾收集,但不能保证执行。JVM 的垃圾收集会收集创建的对象。

Java 本机接口 (JNI)

JNI 将与本机方法库交互并提供执行引擎所需的本机库。

本机方法库

它是执行引擎所需的本机库的集合。