JVM 整体架构图解
想象一下这个场景:你在 IDE 里敲下 System.out.println("Hello"),然后按下运行。几毫秒后,屏幕上出现了 "Hello"。
但在这几毫秒里,JVM 内部发生了什么?
答案是:类加载器把 .class 文件加载进内存 → 字节码执行引擎有条不紊地执行指令 → 垃圾回收器在后台虎视眈眈地盯着每一个对象。
今天,我们就来揭开 JVM 架构的神秘面纱。
JVM 架构全景图
JVM 的整体架构,可以用下面这张图来概括:
┌─────────────────────────────────────────────────────────────────┐
│ JVM 进程空间 │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 类加载子系统 ││
│ │ Bootstrap ClassLoader → ExtClassLoader → AppClassLoader ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 运行时数据区 ││
│ │ ││
│ │ ┌─────────────────────┐ ┌─────────────────────────────┐ ││
│ │ │ 线程私有区域 │ │ 线程共享区域 │ ││
│ │ │ ┌─────────────────┐│ │ ┌───────────────────────┐ │ ││
│ │ │ │ 程序计数器 (PC) ││ │ │ 堆 (Heap) │ │ ││
│ │ │ └─────────────────┘│ │ │ ┌─────┬─────┬─────┐ │ ││
│ │ │ ┌─────────────────┐│ │ │ │Eden │Surv │Surv │ │ ││
│ │ │ │ 虚拟机栈 ││ │ │ │ │ 0 │ 1 │ │ ││
│ │ │ │ (Stack) ││ │ │ │ │ │ │ │ ││
│ │ │ └─────────────────┘│ │ │ ├─────┴─────┴─────┤ │ ││
│ │ │ ┌─────────────────┐│ │ │ │ Old Gen │ │ ││
│ │ │ │ 本地方法栈 ││ │ │ │ │ │ ││
│ │ │ │ (Native Stack) ││ │ │ └───────────────┘ │ ││
│ │ │ └─────────────────┘│ │ └───────────────────────┘ ││
│ │ └─────────────────────┘│ │ ││
│ │ │ │ ┌───────────────────────┐ ││
│ │ │ │ │ 方法区 (Method │ ││
│ │ │ │ │ Area/JDK8+ │ ││
│ │ │ │ │ Metaspace) │ ││
│ │ │ │ └───────────────────────┘ ││
│ │ │ └─────────────────────────────┘│
│ │ ┌─────────────────────┐ │
│ │ │ 直接内存 │ │
│ │ │ (Direct Memory) │ │
│ │ └─────────────────────┘ │
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 执行引擎 ││
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ ││
│ │ │ 解释器 │ │ JIT 编译器 │ │ 垃圾回收器 │ ││
│ │ │ (Interpreter) │ │ (JIT Compiler)│ │ (Garbage │ ││
│ │ │ │ │ C1 / C2 │ │ Collector) │ ││
│ │ └──────────────┘ └──────────────┘ └──────────────────┘ ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 本地接口 / 本地库 ││
│ │ JNI (Java Native Interface) ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘这张图清晰地展示了 JVM 的四大核心组成部分。
一、类加载子系统:JVM 的「入职培训部」
类加载子系统负责将 .class 文件加载到 JVM 中,形成可以被执行的类对象。
它的工作流程是:
.class 文件 → 加载(Loading) → 验证(Verification) → 准备(Preparation)
→ 解析(Resolution) → 初始化(Initialization)加载阶段,类加载器会通过类的全限定名找到对应的 .class 文件,将其字节流转化为方法区的运行时数据结构,并在堆中生成一个 Class 对象作为访问入口。
验证阶段,确保加载的类符合 JVM 规范,不会危害 JVM 安全。
准备阶段,为类的静态变量分配内存,并设置默认初始值(int 设为 0,boolean 设为 false 等)。
解析阶段,将符号引用替换为直接引用。
初始化阶段,执行静态代码块和静态变量的赋值。
关于类加载器的层级关系,我们后面会有专题讲解。
二、运行时数据区:JVM 的「memory」
运行时数据区是 JVM 管理的内存区域,也是面试中的高频考点。
线程私有区域
这些区域每个线程都有自己独立的副本,线程之间互不影响:
| 区域 | 作用 | 大小 |
|---|---|---|
| 程序计数器 | 存储当前线程执行的字节码指令地址 | 极小(几乎可忽略) |
| 虚拟机栈 | 管理方法调用,存储栈帧(局部变量表、操作数栈等) | -Xss 控制,默认 1MB |
| 本地方法栈 | 为 Native 方法服务(JNI 调用) | -Xss 控制或默认 |
线程共享区域
这些区域被所有线程共享:
| 区域 | 作用 | 大小控制 |
|---|---|---|
| 堆(Heap) | 存储对象实例和数组,是 GC 的主要战场 | -Xms / -Xmx |
| 方法区(JDK 7 及之前为 PermGen) | 存储类信息、常量、静态变量、JIT 编译后的代码 | JDK 7: -XX:PermSize/MaxPermSize<br>JDK 8+: -XX:MetaspaceSize/MaxMetaspaceSize |
直接内存
直接内存不属于 JVM 堆,而是操作系统的本地内存。通过 ByteBuffer.allocateDirect() 分配,受 -XX:MaxDirectMemorySize 限制。
NIO 的零拷贝技术就依赖直接内存实现。
三、执行引擎:JVM 的「CPU」
执行引擎是 JVM 的核心,负责执行字节码指令。
解释器 vs JIT 编译器
- 解释器:逐行解释执行字节码,启动快,但执行效率低
- JIT 编译器:将热点代码编译为本地机器码缓存起来,后续直接执行机器码,性能接近 native 代码
HotSpot 默认采用分层编译:
- C1 编译器:快速编译,编译激进程度低
- C2 编译器:深度优化,编译激进程度高
字节码 → 解释执行(同时记录调用次数)
↓ 达到阈值(-XX:CompileThreshold,默认 10000)
C1 即时编译
↓ 达到更高阈值
C2 深度编译垃圾回收器
GC 是执行引擎的重要组成部分,负责自动回收不再使用的对象所占用的内存。
常见的垃圾收集器包括:
- 年轻代:
Serial、ParNew、Parallel Scavenge - 老年代:
Serial Old、Parallel Old、CMS - 全堆:
G1、ZGC、Shenandoah
关于 GC 的详细内容,后面会有系列专题。
四、本地接口与本地库
JNI(Java Native Interface)提供了 Java 与本地代码(C/C++)交互的能力。
当你调用 System.loadLibrary() 加载一个 native 库时,就通过 JNI 与本地方法栈交互。很多 Java 底层库(如 java.io 的部分实现)内部就调用了 native 方法。
各区域与硬件的对应关系
从物理机器的角度看 JVM:
物理机 JVM
─────────────────────────────
CPU → 执行引擎(解释器 + JIT)
寄存器 → 程序计数器
内存 → 堆 + 方法区
缓存 → TLAB(线程本地分配缓冲)
磁盘 → 直接内存(NIO)理解这个对应关系,有助于你在排查问题时准确定位瓶颈在哪里。
总结
JVM 架构的核心就是「加载—存储—执行—回收」这四个环节的循环:
- 类加载子系统把
.class加载进内存 - 运行时数据区为对象和代码提供存储空间
- 执行引擎负责执行字节码
- 垃圾回收器负责回收不再使用的内存
面试中,面试官可能会从以下几个角度追问:
- 「线程私有区域和共享区域有什么区别?」
- 「为什么堆需要 GC,而栈不需要?」
- 「JIT 编译和解释执行各有什么优劣?」
留给你的思考题:
如果你发现一个 Java 进程占用了 8GB 内存,但堆只设置了 4GB,这多出来的 4GB 去了哪里?
提示:直接内存、Metaspace、线程栈、本地代码映射等都可能占用内存。下篇文章我们会详细讲解运行时数据区的各个组成部分。
