JVM (Java 虚拟机) 架构

2025年3月5日 | 阅读7分钟

JVM (Java 虚拟机) 是一个抽象机器。它是一个规范,提供了一个可以执行 Java 字节码的运行时环境。

JVM 可用于许多硬件和软件平台(即,JVM 是平台相关的)。

JVM 被设计为平台相关的,这意味着它们是为特定的硬件和软件平台量身定制的。这种灵活性允许 JVM 实现针对各种系统优化性能和兼容性,从而使 Java 应用程序能够在不同的环境中一致运行。

什么是 JVM

它是一个

  1. 规范,规定了 Java 虚拟机的工作方式。然而,实现提供者可以独立选择算法。Oracle 和其他公司都提供了它的实现。
  2. 实现。它的实现被称为 JRE (Java 运行时环境)。
  3. 运行时实例。每当我们在命令提示符下输入 java 命令来运行 Java 类时,就会创建一个 JVM 实例。

它的作用是什么?

JVM 执行以下操作:

  • 加载代码
  • 验证代码
  • 执行代码
  • 提供运行时环境

JVM 为以下内容提供定义:

  • 内存区域
  • 类文件格式
  • 寄存器集
  • 垃圾收集堆
  • 致命错误报告等。

JVM 架构

JVM Architecture

1) 类加载器

类加载器是 JVM 的一个子系统,用于加载类文件。每当我们运行 Java 程序时,它首先由类加载器加载。Java 中有三个内置的类加载器。

  1. 引导类加载器 (Bootstrap ClassLoader):它是第一个类加载器,是扩展类加载器的超类。它加载包含 Java 标准版所有类文件的 rt.jar 文件,例如 java.lang 包类、java.net 包类、java.util 包类、java.io 包类、java.sql 包类等。
  2. 扩展类加载器 (Extension ClassLoader):它是引导类加载器的子类加载器,也是系统类加载器的父类加载器。它加载位于 $JAVA_HOME/jre/lib/ext 目录中的 jar 文件。
  3. 系统/应用程序类加载器 (System/Application ClassLoader):它是扩展类加载器的子类加载器。它从类路径中加载类文件。默认情况下,类路径设置为当前目录。您可以使用 "-cp" 或 "-classpath" 开关更改类路径。它也称为应用程序类加载器。
  4. 运行时数据区:JVM 在程序执行期间为各种运行时数据区分配内存。这些包括方法区、堆、栈、PC(程序计数器)寄存器和本地方法栈。方法区存储类结构、方法代码、静态变量和常量池。堆是对象分配的地方,而栈保存方法调用帧。PC 寄存器跟踪当前正在执行的 JVM 指令,本地方法栈用于执行本地方法。
  5. 执行引擎:执行引擎负责执行编译后的 Java 字节码。它由两个组件组成:解释器和即时 (JIT) 编译器。解释器逐条读取和执行字节码指令,而 JIT 编译器将频繁执行的字节码序列编译为本机机器码以提高性能。
  6. 垃圾收集器:JVM 包含一个垃圾收集器,负责回收不再使用的对象占用的内存。垃圾收集器识别并移除不可达对象,从而为新分配腾出内存。有各种垃圾收集算法可用,每种算法在性能和内存开销方面都有其自身的权衡。
  7. 本地方法接口 (JNI):JNI 使 Java 代码能够与本地库交互并执行 Java 语言不直接支持的操作。它允许 Java 应用程序调用用 C 和 C++ 等语言编写的函数,从而提供灵活性并访问平台特定的功能。

文件名:ClassLoaderExample.java

输出

sun.misc.Launcher$AppClassLoader@4e0e2f2a
null

说明

此 Java 代码演示了如何打印给定类的类加载器名称。在 ClassLoaderExample 类的 main 方法中,我们使用 ClassLoaderExample.class 语法获取代表 ClassLoaderExample 类本身的 Class 对象的引用。然后我们在这个 Class 对象上调用 getClassLoader() 方法来检索加载 ClassLoaderExample 类的类加载器。

这个类加载器通常是系统/应用程序类加载器,因为它负责加载用户定义的类。接下来,我们使用 String.class.getClassLoader() 打印 String 类的类加载器名称。然而,这返回 null,因为 String 是 Java 标准版的一部分的内置类,存在于 rt.jar 中。由于 rt.jar 是由引导类加载器加载的,它是由本地代码编写的,因此它没有关联的 Java 类加载器对象,因此结果为 null。

这些是 Java 提供的内部类加载器。如果我们想创建自己的类加载器,我们需要扩展 ClassLoader 类。

2) 类(方法)区

类(方法)区存储每个类的结构,例如运行时常量池、字段和方法数据以及方法代码。

  1. 运行时常量池:运行时常量池是类(方法)区的一部分,由常量池条目组成。这些用于存储符号引用、字面量以及类所需的其他常量值。这些包括类和接口名称、方法和字段名称、字符串字面量和数字字面量。
  2. 字段数据:类(方法)区还存储关于类中声明的字段的信息,包括它们的名称、类型和访问修饰符。JVM 在运行时使用此数据访问和操作对象字段。
  3. 方法数据:除了字段数据之外,类(方法)区还包含关于类中声明的方法的信息,包括它们的名称、返回类型、参数类型和字节码指令。JVM 在程序执行期间使用此信息调用方法和执行字节码指令。

3) 堆

堆是分配对象的运行时数据区。

  1. 对象分配:堆负责为程序执行期间动态创建的对象分配内存。当使用 new 关键字或调用构造函数实例化对象时,会在堆上分配内存来存储对象的数据。
  2. 垃圾收集:堆由 JVM 的垃圾收集器管理,该收集器定期扫描堆以查找不再使用或应用程序不可达的对象。垃圾收集涉及回收这些未使用对象占用的内存,为新分配腾出空间。
  3. 堆结构:堆通常分为两个主要区域:新生代 (Young Generation) 和老年代 (Old Generation)(也称为终身代 Tenured Generation)。新生代进一步分为伊甸区 (Eden Space) 和幸存区 (Survivor Spaces),而老年代包含经过多次垃圾收集周期后仍然存活的长期对象。

4) 栈

Java 栈存储帧。它保存局部变量和部分结果,并在方法调用和返回中发挥作用。

每个线程都有一个私有的 JVM 栈,与线程同时创建。

每次调用方法时都会创建一个新帧。当方法调用完成时,该帧被销毁。

  1. 帧结构:每个帧包含局部变量、操作数栈和对正在执行的方法的运行时常量池的引用。局部变量存储方法参数和局部变量,而操作数栈用于在方法执行期间的中间结果和操作数操作。
  2. 方法调用:栈在方法调用和返回中起着至关重要的作用。当调用方法时,会创建一个新帧并推入栈中。方法完成后,该帧会从栈中弹出,并将控制权返回给调用方法。

5) 程序计数器寄存器

PC(程序计数器)寄存器包含当前正在执行的 Java 虚拟机指令的地址。

程序计数器 (PC) 寄存器是 JVM 内的一个特殊寄存器,它包含当前正在执行指令的内存地址。

  1. 指令指针:PC 寄存器充当指令指针,指导 JVM 按照正在执行的字节码指令序列进行操作。
  2. 线程特定:与 Java 栈一样,Java 应用程序中的每个线程都有自己的 PC 寄存器,允许多个线程并发执行指令而不相互干扰。

6) 本地方法栈

它包含应用程序中使用的所有本地方法。

本地方法栈是 JVM 中的一个内存区域,用于执行本地方法,这些方法是用 Java 以外的语言编写的,例如 C 或 C++。有关本地方法栈的其他信息包括:

本地方法调用:当 Java 应用程序调用本地方法时,执行流会转换到本地方法栈,在那里执行本地方法的代码。

7) 执行引擎

它包含

  1. 一个虚拟处理器
  2. 解释器:读取字节码流然后执行指令。
  3. 即时 (JIT) 编译器:它用于提高性能。JIT 会同时编译具有相似功能的字节码部分,从而减少编译所需的时间。在这里,“编译器”一词指的是从 Java 虚拟机 (JVM) 的指令集到特定 CPU 指令集的转换器。

8) Java 本地接口

Java 本地接口 (JNI) 是一个框架,提供了一个接口,用于与用其他语言(如 C、C++、汇编等)编写的其他应用程序进行通信。Java 使用 JNI 框架将输出发送到控制台或与操作系统库交互。


下一个主题Java 变量