什么是Java虚拟机(JVM)?有什么作用?

什么是Java虚拟机(JVM)?有什么作用?

虚拟机概述与执行流程

定义:运行 Java 字节码的抽象计算机,屏蔽底层 OS 差异,实现“一次编译,到处运行”。

执行流程:

加载:ClassLoader 加载 .class 文件至元空间。

验证:字节码安全性检查(格式、语义、操作数栈)。

执行:解释器执行 + JIT(C2/Graal)编译热点代码为本地机器码。

卸载:类不再引用时卸载元数据。

核心作用:内存自动管理(GC)、即时编译优化(JIT)、安全沙箱、跨平台。

JDK 21+ 演进:引入虚拟线程(Project Loom),大幅降低线程上下文切换开销,重塑高并发模型。

内存结构:逻辑分代与物理分区

堆(Heap):对象实例存储区,GC 核心区域。

逻辑分代(Logical Generation):

定义:根据对象存活时间划分的概念模型(新生代 Young、老年代 Old)。

实现:现代收集器(G1/ZGC/Shenandoah)中,分代是逻辑标签,而非物理隔离。

物理分区(Physical Partition):

定义:堆内存实际切分的管理单元。

规则:一个分区在同一时刻,只能属于新生代 或 老年代 中的一种(互斥性)。

动态性:分区角色可随 GC 动态变更(如 Region 从 Young 变为 Old)。

实现差异:

G1:物理单元为 Region(固定大小 1MB~32MB)。

ZGC:物理单元为 ZPage(动态大小,底层页组合)。

Shenandoah:物理单元为 Region(固定大小,类似 G1)。

元空间(Metaspace):JDK 8+ 取代永久代,使用本地内存存储类元数据、常量池。

栈(Stack):线程私有,存储栈帧(局部变量、操作数栈、动态链接)。

直接内存(Direct Memory):NIO 使用,不受堆大小限制,受本机内存限制。

程序计数器:记录当前线程执行的字节码行号。

垃圾回收算法 vs 垃圾收集器

关系:算法是理论,收集器是实现。

算法:解决“如何识别垃圾”和“如何清理内存”的逻辑(如标记、复制)。

收集器:工程组件,组合多种算法以满足吞吐量、延迟、内存约束。

算法与收集器对应:

复制算法:G1 新生代、ZGC 新生代(重定位)。

标记 - 整理:G1 老年代、ZGC 全堆(并发整理)、Shenandoah 全堆。

标记 - 清除:现代收集器已基本弃用(会产生碎片),仅特定内部阶段使用(如 CMS 老年代,已移除)。

现代主流垃圾收集器特性 G1 GC ZGC Shenandoah默认版本 JDK 9+ 默认 JDK 21+ 默认(分代) OpenJDK 发行版默认(分代 JDK 24+)分代模式 原生逻辑分代 JDK 21+ 分代默认(JDK 11-17 为单代) JDK 24+ 分代目标(JDK 12-23 主要为单代)核心算法 分区复制 + 标记整理 并发标记 + 并发整理(染色指针) 并发标记 + 并发整理(Brooks 指针)停顿时间 毫秒级(可预测) 亚毫秒级(<1ms) 亚毫秒级(<10ms)堆大小建议 < 50GB TB 级 TB 级适用场景 通用服务端,吞吐量敏感 低延迟,超大内存 低延迟,OpenJDK 生态JDK 21+ 演进 优化并行度,减少停顿 分代成为默认,吞吐量提升 4 倍 优化并发标记,适配 ARM/分代演进

CMS:已在 JDK 14 移除,不再可用。

Parallel GC:仍保留,仅推荐批处理场景(吞吐量优先,不在乎停顿)。

分代趋势:JDK 21 后,低延迟收集器全面转向“逻辑分代”以提升吞吐量。

垃圾回收核心机制

对象存活判断:

主流算法:可达性分析(Java 采用)。从 GC Roots(栈帧、静态变量、常量)搜索,不可达即垃圾。

非主流算法:引用计数(Java 堆未采用)。无法解决循环引用,仅用于 NIO 等内部模块。

标记实现技术:

双色标记:早期 Serial GC(黑/白),不支持并发。

三色标记:并发 GC 专用(白/灰/黑)。解决并发漏标,需配合屏障(写屏障/读屏障)。

指针技术:

压缩指针:所有收集器通用。64 位压缩为 32 位,节省内存(堆<32GB 自动启用)。

染色指针:ZGC 专用。指针高位存储标记状态,支持并发整理(无需对象头存标记)。

分代回收策略:

新生代(Eden + Survivor):复制算法。对象朝生夕死,复制存活对象成本低,无碎片。

老年代:标记 - 整理。对象存活率高,整理消除碎片,避免频繁复制。

大对象:

G1:直接进入 Humongous Region。

ZGC:由大 ZPage 承载(无 Humongous 概念,更灵活)。

ZGC 特殊性:

停顿与堆大小无关:停顿时间仅取决于 GC Roots 数量,而非堆中对象总数。

并发整理:通过读屏障(Load Barriers)和染色指针,在应用运行时移动对象。

物理 vs 逻辑分代本质:

传统(物理):内存地址固定隔离(如 Parallel GC),新生代满无法借用老年代空间。

现代(逻辑):内存单元标签化管理(如 G1/ZGC),空闲单元可动态借给新生代,空间利用率更高。

逃逸分析与 JIT 优化

逃逸分析:JIT 分析对象是否逃出方法/线程。

优化手段:

栈上分配:未逃逸对象直接在栈帧

配,随方法结束销毁,无 GC 压力。

标量替换:对象拆解为基本变量存储在栈帧,不创建对象头。

同步消除:线程私有对象无需锁,移除 synchronized。

JDK 21+:配合虚拟线程(Virtual Threads),进一步优化栈内存布局,减少载体线程压力,提升高并发下的内存效率。