走进底层 - JVM工作原理入门指南
Java 之所以能够实现“一次编写,到处运行”(Write Once, Run Anywhere, WORA),核心在于 Java 虚拟机(JVM, Java Virtual Machine)。JVM 是 Java 程序的运行环境,负责将 Java 字节码(
.class
文件)转换成机器码并执行。
本文将从 JVM 的基本结构、内存管理、类加载机制 和 垃圾回收(GC) 四个方面,初步了解 JVM 的工作原理。
1. JVM 的基本结构
JVM 主要由以下几个核心部分组成:
- 类加载子系统
- 负责加载
.class
文件到内存,并生成对应的Class
对象。
- 负责加载
- 运行时数据区
- 包括 方法区(Method Area)、堆(Heap)、虚拟机栈(VM Stack)、本地方法栈(Native Method Stack) 和 程序计数器(Program Counter Register)。
- 执行引擎
- 负责执行字节码,包括 解释器(Interpreter) 和 即时编译器(JIT Compiler)。
- 本地方法接口
- 用于调用 C/C++ 编写的本地库。
- 用于调用 C/C++ 编写的本地库。
2. 运行时数据区(内存模型)
JVM 的内存主要分为以下几个部分:
(1) 方法区(Method Area)
- 用于存储已被加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据。在 JDK 8 及之后的版本中,方法区使用元空间(Metaspace)来实现,元空间并不在堆内存中,而是使用本地内存。常量池是方法区的一部分,它存储了各种常量,包括字符串常量、基本数据类型常量等
(2) 堆
- 所有对象实例和数组都在堆上分配,是垃圾回收(GC)的主要区域,也是jvm的最大区域。
- 分为:
- 新生代:存放新创建的对象,分为 Eden 区 和 Survivor 区(S0、S1)。
- 老年代:存放长期存活的对象。
- 元空间(JDK 8+):取代永久代,存储类元数据。
(3) 虚拟机栈
- 每个线程在运行时都会有一个对应的 Java 虚拟机栈。它由一个个栈帧(Stack Frame)组成,栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。当一个方法被调用时,就会创建一个新的栈帧并压入栈顶,方法执行完毕后,栈帧就会从栈顶弹出。局部变量表用于存储方法中的局部变量,包括基本数据类型和对象引用。操作数栈则用于方法执行过程中的运算操作
(4) 本地方法栈
- 类似于虚拟机栈,但用于执行 本地方法(如 C/C++ 代码)。
(5) 程序计数器
- 可以理解为一个指针,它记录了当前线程正在执行的字节码指令的地址。如果当前线程正在执行一个 Java 方法,那么程序计数器中保存的就是正在执行的字节码指令的地址;如果正在执行的是本地方法(Native Method),那么程序计数器的值就是未定义的。由于 Java 是多线程的,每个线程都有自己独立的程序计数器,这样当线程切换时,就能够恢复到正确的执行位置
3. 类加载机制
JVM 加载 .class
文件的过程分为 加载、连接、初始化 三个阶段:
(1) 加载(Loading)
- 通过 类加载器(ClassLoader) 查找
.class
文件并加载到内存。 - 类加载器分类:
- Bootstrap ClassLoader(启动类加载器):加载
JAVA_HOME/lib
下的核心类(如java.lang.*
)。 - Extension ClassLoader(扩展类加载器):加载
JAVA_HOME/lib/ext
下的扩展类。 - Application ClassLoader(应用类加载器):加载用户类路径(
classpath
)下的类。 - 自定义 ClassLoader:用户可继承
ClassLoader
实现自己的加载逻辑。一般用于服务器的加载器(因为同一个类名不会被反复加载,因此为了防止同类名不同数据的类被省略,需要自定义一个类实现多个对象)
- Bootstrap ClassLoader(启动类加载器):加载
(2) 连接
- 验证:检查字节码是否符合 JVM 规范。
- 准备:为静态变量分配内存并赋默认值(如
int
默认0
) - 解析:将符号引用(如
java.lang.Object
)替换为直接引用(内存地址),也就是类名等字符转换为地址
(3) 初始化
- 执行静态代码块(
static {}
)和静态变量赋值。 - 采用 双亲委派机制 避免重复加载类。
4. 垃圾回收(GC)
JVM 自动管理内存,通过 垃圾回收器 回收不再使用的对象。
(1) 如何判断对象可回收?
- 引用计数法(Python 使用):对象被引用时计数+1,为 0 时回收(但 Java 不采用,因为无法解决循环引用问题)。
- 可达性分析:从 GC Roots(如虚拟机栈、静态变量、本地方法栈引用的对象)出发,标记所有可达对象,不可达的视为垃圾。
(2) 垃圾回收算法
- 标记-清除:标记垃圾对象后直接清除,但会产生内存碎片。
- 复制:将存活对象复制到另一块内存(用于 新生代,如
Eden → Survivor
)。 - 标记整理:标记存活对象后整理内存(用于 老年代)。
5. 执行引擎
JVM 执行字节码的方式:
- 解释执行:逐行解释字节码,执行速度较慢。
- 即时编译(JIT):将热点代码(HotSpot)编译成机器码,提高执行速度
总结
组成部分 | 核心功能 |
---|---|
类加载子系统 | 加载 .class 文件到内存 |
运行时数据区 | 存储类信息、对象、方法调用等 |
执行引擎 | 解释或编译执行字节码 |
垃圾回收(GC) | 自动回收无用对象,释放内存 |
JVM 是 Java 生态的核心,理解其工作原理有助于优化代码、排查内存泄漏(OOM)和提升性能。