2026年7月11日 · 5 分钟阅读
JVM 入门:Java 虚拟机、类加载机制与双亲委派模型
梳理 JVM 基本概念、Java 技术体系、类加载时机、类加载过程、类加载器和双亲委派模型。
学习 JVM,不能只把它当成面试题。线程死锁、内存溢出、性能抖动、类冲突、线上排查,很多 Java 项目的重难点最终都会落到 JVM 这一层。
JVM 广义上是一套虚拟机规范,狭义上通常指 JDK 里的具体虚拟机实现。日常说的 JVM,大多指 HotSpot。
为什么要学习 JVM
JVM 解决的是 Java 程序如何运行的问题。
如果只会写业务代码,遇到下面这些问题时就很容易停在表面:
- 为什么一个类明明存在,运行时却报
ClassNotFoundException。 - 为什么两个线程会死锁。
- 为什么堆内存还有空间,应用却频繁 Full GC。
- 为什么修改了 JVM 参数,却没有任何效果。
- 为什么同样的代码在不同 JDK 版本下表现不一样。
JVM 知识不是为了让代码显得复杂,而是为了在问题出现时知道从哪里下手。
Java 技术体系
Java 技术体系不只是 Java 语言本身,通常包括四部分:
| 组成 | 说明 |
|---|---|
| Java 程序设计语言 | 语法、类型系统、面向对象能力 |
| Java 类库 API | JDK 提供的基础类库 |
| 第三方类库 | 商业机构和开源社区提供的库 |
| JVM | 执行字节码、管理内存、提供运行时环境 |
Kotlin、Groovy、Scala、Clojure 等语言也可以运行在 JVM 上,因为 JVM 真正执行的是字节码,而不只服务于 Java 源代码。
类什么时候会被加载
类加载不是 Java 文件写好后就立刻发生,而是在运行时按需进行。
常见触发时机有四类:
- 遇到
new、getstatic、putstatic、invokestatic等指令时,如果类还没有初始化,就会触发初始化。 - 使用反射调用类时,如果类还没有初始化,也会触发初始化。
- 初始化子类时,如果父类还没有初始化,会先初始化父类。
- JVM 启动时,会先初始化用户指定的主类,也就是包含
main方法的类。
理解这些时机,对排查静态变量初始化顺序、类加载异常和反射行为很有帮助。
类加载过程
类加载完整生命周期可以概括为:
加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载
其中前五步最关键。
| 阶段 | 主要工作 |
|---|---|
| 加载 | 根据类的全限定名获取二进制字节流,并创建 Class 对象 |
| 验证 | 确认字节码符合 JVM 规范,不会破坏虚拟机安全 |
| 准备 | 为类变量分配内存,并设置默认初始值 |
| 解析 | 将常量池中的符号引用转换为直接引用 |
| 初始化 | 执行类构造器 <clinit>,为静态变量赋程序指定值 |
加载阶段主要做三件事:
- 通过类的全限定名获取 class 文件的二进制字节流。
- 把字节流里的静态数据结构转换为方法区的运行时数据结构。
- 在堆中创建代表该类的
java.lang.Class对象。
准备阶段和初始化阶段的区别
准备阶段容易和初始化阶段混淆。
比如:
public class User {
public static int count = 10;
}
在准备阶段,count 会先被设置为默认值 0;到了初始化阶段,才会执行赋值逻辑,把它设置为 10。
这也是为什么 JVM 类加载看起来抽象,但其实和代码执行顺序强相关。
类加载器有哪些
JVM 的类加载通过 ClassLoader 完成。常见类加载器包括:
| 类加载器 | 负责范围 |
|---|---|
| Bootstrap ClassLoader | 加载 JDK 核心类库 |
| Extension ClassLoader | 加载扩展类库,JDK 9 后机制有所变化 |
| Application ClassLoader | 加载应用 classpath 下的类 |
| 自定义 ClassLoader | 按业务需要加载指定路径、网络、加密 class 等 |
自定义类加载器通常继承 ClassLoader,重写类查找逻辑,用于插件化、热部署、隔离不同版本依赖等场景。
双亲委派模型
双亲委派模型的核心规则是:一个类加载器收到加载请求后,先把请求交给父加载器处理;父加载器无法加载时,子加载器才尝试自己加载。
流程大致是:
应用类加载器 -> 扩展类加载器 -> 启动类加载器
如果父加载器能加载,就由父加载器完成;如果父加载器加载不了,才向下回到子加载器。
双亲委派解决什么问题
双亲委派主要解决两个问题。
第一是安全。应用不能随便写一个 java.lang.String 替换 JDK 自带的核心类。
第二是类型一致性。同一个类如果被不同类加载器重复加载,JVM 会认为它们是不同类型。双亲委派可以尽量保证核心类由统一加载器加载。
什么时候会打破双亲委派
双亲委派不是绝对不能打破。常见场景包括:
- SPI 机制需要父加载器加载的代码反过来调用子加载器中的实现类。
- Web 容器需要隔离不同应用的依赖。
- 插件系统需要独立加载、卸载插件。
- 热部署需要重新加载某些 class。
打破双亲委派不是为了炫技,而是为了解决隔离、扩展和动态加载问题。
小结
JVM 入门可以先抓住一条线:
- JVM 是 Java 程序运行的基础环境。
- 类不是一次性全部加载,而是在运行时按需加载。
- 类加载过程包括加载、验证、准备、解析、初始化。
- 类加载器负责把 class 字节码加载进 JVM。
- 双亲委派模型保证核心类安全和类型一致性。
理解类加载之后,再看运行时数据区、对象创建和 GC,就会顺很多。