JVM(Java Virtual Machine,Java 虚拟机)是 Java 语言跨平台特性的核心,它负责将字节码(.class 文件)解释或编译为机器码并执行。以下从核心角度详解 JVM:
一、JVM 的作用与地位
- 跨平台基石:"一次编写,到处运行"(Write Once, Run Anywhere)的实现者,通过屏蔽不同操作系统的底层差异,使字节码在任何安装了 JVM 的平台上运行。
- 内存管理:自动负责内存分配与回收(垃圾回收),减少手动内存操作的风险。
- 执行引擎:将字节码转换为机器码(解释执行或即时编译 JIT)。
二、JVM 内存结构(基于 Java 8 及以后)
JVM 内存分为线程私有 和线程共享两大区域:
1. 线程私有区域(随线程创建 / 销毁)
-
**程序计数器(Program Counter Register)**记录当前线程执行的字节码指令地址(如分支、循环、跳转等),是唯一不会 OOM 的区域。
-
**虚拟机栈(VM Stack)**存储方法调用的栈帧(Stack Frame),每个栈帧包含:
- 局部变量表(方法内变量)
- 操作数栈(计算过程的临时数据)
- 动态链接(指向常量池的引用)
- 方法返回地址可能抛出 :
StackOverflowError
(栈深度溢出)、OutOfMemoryError
(栈扩展失败)。
-
**本地方法栈(Native Method Stack)**类似虚拟机栈,但为 Native 方法(如 C/C++ 实现的方法)服务,HotSpot 将其与虚拟机栈合并实现。
2. 线程共享区域(随 JVM 启动 / 关闭)
-
堆(Heap)
- JVM 中最大的内存区域,所有对象实例及数组均在此分配。
- 是垃圾回收(GC)的主要区域,可分为:
- 新生代(Eden 区 + 两个 Survivor 区,比例通常 8:1:1)
- 老年代(存放存活久的对象)可能抛出 :
OutOfMemoryError: Java heap space
。
-
方法区(Method Area)
- 存储类信息(结构、方法、字段)、常量、静态变量、即时编译后的代码等。
- Java 8 后由元空间(Metaspace) 实现,取代永久代,元空间使用本地内存,默认无上限(可配置)。可能抛出 :
OutOfMemoryError: Metaspace
(元空间不足)。
-
运行时常量池(Runtime Constant Pool)
- 方法区的一部分,存放类加载时从字节码常量池解析出的字面量(如字符串)和符号引用(如类名、方法名)。
- 字符串常量池(String Pool)是其重要组成,JDK 7 后移至堆中。
三、类加载机制
JVM 将.class 文件加载到内存并生成可执行代码的过程,分为 5 步:
-
加载(Loading) 通过类全限定名(如
java.lang.String
)获取二进制字节流,将其转换为方法区的运行时数据结构,并在堆中生成Class
对象作为访问入口。 -
**验证(Verification)**确保字节码符合 JVM 规范(如格式正确、安全校验),防止恶意字节码攻击。
-
准备(Preparation) 为类的静态变量分配内存并设置初始默认值 (如
int
为 0,boolean
为 false),不包含初始化语句(如static int a = 10
中的 10 在初始化阶段赋值)。 -
**解析(Resolution)**将常量池中的符号引用(如类名、方法名)替换为直接引用(内存地址)。
-
初始化(Initialization) 执行类构造器
<clinit>()
方法:- 合并静态变量赋值语句和静态代码块(按顺序执行)。
- 父类
<clinit>()
先于子类执行。
四、垃圾回收(GC)
JVM 自动回收不再被引用的对象内存,核心步骤:
1. 如何判断对象可回收?
- 引用计数法:对象被引用时计数 + 1,失效时 - 1,计数为 0 则可回收(缺点:无法解决循环引用)。
- 可达性分析:以 "GC Roots" 为起点(如虚拟机栈引用的对象、静态变量、常量等),不可达的对象标记为可回收。
2. 垃圾回收算法
- 标记 - 清除(Mark-Sweep):标记可回收对象后直接清除(缺点:产生内存碎片)。
- 标记 - 复制(Mark-Copy):将存活对象复制到新区域,清除原区域(适合新生代,如 Eden 区到 Survivor 区)。
- 标记 - 整理(Mark-Compact):标记后将存活对象向一端移动,再清除边界外内存(适合老年代)。
3. 常见垃圾收集器
- SerialGC:单线程 GC,适用于简单应用(Client 模式)。
- ParallelGC:多线程 GC,注重吞吐量(吞吐量 = 运行用户代码时间 / 总时间)。
- CMS(Concurrent Mark Sweep):并发标记清除,注重低延迟(停顿时间短)。
- G1(Garbage-First):将堆分为多个 Region,优先回收垃圾多的区域,平衡吞吐量和延迟(Java 9 后默认)。
- ZGC/Shenandoah:超低延迟 GC(毫秒级以下),适合大内存场景。
五、执行引擎
将字节码转换为机器码的核心组件:
- 解释器:逐行解释字节码为机器码,启动快但执行慢。
- JIT 编译器(即时编译器):将热点代码(频繁执行的代码)编译为本地机器码缓存,提高执行效率(HotSpot 的 C1 编译器针对客户端,C2 针对服务端)。
- 混合模式:JVM 默认采用解释 + JIT 编译结合的方式,兼顾启动速度和执行效率。
六、JVM 参数调优
通过 JVM 参数配置内存大小、GC 策略等,常见参数:
- 堆内存:
-Xms
(初始堆大小)、-Xmx
(最大堆大小,建议与-Xms
一致避免频繁扩容)。 - 新生代:
-Xmn
(新生代大小)、-XX:SurvivorRatio=8
(Eden 与 Survivor 比例)。 - 元空间:
-XX:MetaspaceSize
、-XX:MaxMetaspaceSize
。 - GC 日志:
-XX:+PrintGCDetails
、-XX:+PrintGCTimeStamps
(分析 GC 问题)。 - 垃圾收集器:
-XX:+UseG1GC
(使用 G1)、-XX:+UseZGC
(使用 ZGC)。
总结
JVM 是 Java 生态的核心,其内存管理、类加载、垃圾回收和执行引擎共同支撑了 Java 的跨平台性和安全性。理解 JVM 原理不仅有助于排查内存泄漏、OOM 等问题,也是高性能 Java 应用调优的基础。