- 教程
- 知识库
【免费下载链接】tech-interview-for-developer
👶🏻 신입 개발자 전공 지식 & 기술 면접 백과사전 📖
本文基于 tech-interview-for-developer 仓库中的 Language/[java] 자바 가상 머신(Java Virtual Machine).md 整理扩充,并融合仓库内 Java 编译过程、Java 版本特性变化、Interned String 与 Java 中的 Thread 等姊妹文档,形成一套面向面试复习与工程实践的 JVM 体系化解读。读完本文,你将掌握:JVM 为什么能让 Java"一次编写、到处运行"、一个
.class文件从类加载到执行引擎的完整生命周期、五大运行时数据区域的职责与共享关系,以及垃圾回收(GC)的基本原理。
一、JVM 是什么:一个"程序执行程序"的规范与运行时
1.1 定义与定位
JVM(Java Virtual Machine,자바 가상 머신)是一个管理系统内存、并为基于 Java 的应用程序提供可移植执行环境的虚拟机。官方层面的准确说法是:
JVM 是用于执行代码,并为该代码提供运行时环境(Runtime Environment)的程序规范(Specification)。
也就是说,JVM 首先是一份规范,规定了字节码该如何被加载、验证、执行以及内存该如何划分;而我们在实际运行 Java 程序时见到的java进程,则是这份规范的一种具体实现(如 HotSpot VM)。
开发者口中常说的"JVM",通常特指运行在某个设备上的进程(Process),尤其是代表并控制着 Java 应用资源的那个服务进程。该进程通过类加载器(Class Loader)读取 Java 应用程序的类文件,并与 Java API 一起执行。它在 Java 程序与操作系统(OS)之间扮演中间人(中介)角色,从而让 Java 应用摆脱对特定 OS 的依赖,实现跨平台复用。
1.2 两大核心功能
JVM 具备的功能可以概括为两点:
- 跨平台执行:让 Java 程序能在任何设备、任何操作系统上运行(这正是"Write Once, Run Anywhere"的技术根基);
- 内存管理与优化:负责程序内存的管理与优化(包括运行时内存区域的划分、垃圾回收等)。
1.3 补充:为什么 JVM 能实现 OS 无关
Java 之所以具备 OS 无关的特性,关键在于 Java 源码并不是直接编译成机器码,而是先编译成 JVM 能理解的字节码(Bytecode)。字节码与具体硬件/OS 无关,真正与 OS 打交道的是 JVM 本身。因此同一份.class文件,只需在不同平台上安装对应的 JVM 实现,即可原样运行——这就是 Java 编译过程 中反复强调的"OS 独立性"的由来。
二、JVM 中的内存管理:整体执行流程
JVM 执行中最常见的交互操作,就是检查堆(Heap)与栈(Stack)的内存使用情况。理解内存管理,首先要理解一条 Java 程序的完整执行链路。
2.1 五步执行过程
- 向 OS 申请内存:程序启动后,JVM 从操作系统那里申请该程序所需的内存,并按用途将这些内存划分为多个区域进行管理(即下面的 Runtime Data Areas);
- 源码编译为字节码:Java 编译器(JAVAC / Java Compiler)读取 Java 源码(
.java),将其转换为 Java 字节码(.class文件); - 类加载:通过类加载器(Class Loader)将转换好的
.class文件加载到 JVM 内存区域中; - 字节码解释执行:加载后的
.class文件由执行引擎(Execution Engine)进行解释(Interpreter)与编译(JIT Compiler); - 内存区域中实际执行:被解释/编译的字节码被放置在内存区域中完成真正的运算;在此过程中,JVM 按需执行**线程同步(Thread Synchronization)与垃圾回收(Garbage Collection)**等内存管理任务。
.java 源码 │ javac 编译 ▼ .class 字节码 │ 类加载器(Class Loader)动态加载 + 链接 ▼ 运行时数据区域(Runtime Data Areas) │ 执行引擎(Execution Engine)解释 / JIT 编译 ▼ 真正执行 + GC / 线程同步2.2 两个关键角色
Java 编译器(JAVAC):负责把 Java 源码(.java)转换为字节码(.class)。字节码的每条指令由1 字节大小的 Opcode(操作码)与**附加的操作数(Operand)**组成,此时它既不是人能读的源码,也不是 CPU 能直接执行的机器码,而是"JVM 能理解的代码"。
类加载器(Class Loader):JVM 在运行时第一次引用某个类时才去加载该类并放置到内存区域中,这种**动态加载(Dynamic Loading)**机制正是由类加载器完成的。关于它内部更细的加载、验证、准备、解析、初始化五阶段,见下文第三章。
三、类加载器:动态加载与链接的五阶段
Java 编译过程 将类加载器的详细动作拆解为五个阶段,这也是 JVM 面试的高频考点:
- 加载(Load):读取类文件并将其加载到 JVM 内存中;
- 验证(Verify):检查该类是否按照Java 语言规范(Java Language Specification)与JVM 规范所规定的内容正确构成,防止不合法或恶意的字节码破坏运行时;
- 准备(Prepare):为类所需的内存进行分配(字段、方法、接口等),并为静态字段赋予 JVM 默认的零值;
- 解析(Resolve):将类常量池(Constant Pool)中的符号引用(Symbolic Reference)替换为直接引用(Direct Reference);
- 初始化(Initialize):将类变量(static 字段)初始化为合适的值(执行
<clinit>,即静态初始化块与静态字段的赋值)。
与类加载器强相关的另一个内存细节是常量池:字符串字面量(String Literal)之所以能用
==比较出相同引用,正是因为 JVM 通过常量池统一管理字符串对象——详见仓库内 Interned String in Java(其中解释了new String("HARIBO")与字面量"HARIBO"引用不等、String.valueOf()返回this的原因)。
四、运行时数据区域(Runtime Data Areas):五大内存分区
运行时数据区域是JVM 运行在操作系统之上时从 OS 分配到的内存区域,共划分为 5 个部分:
| 区域 | 线程共享? | 主要职责 |
|---|---|---|
| PC 寄存器(Program Counter Register) | 线程私有 | 记录当前线程正在执行的 JVM 指令地址 |
| JVM 栈(JVM Stack / 栈 Area) | 线程私有 | 存储局部变量、方法参数、方法信息、临时数据等 |
| 本地方法栈(Native Method Stack) | 线程私有 | 执行由真实机器码(Native Code)编写的程序 |
| 堆(Heap) | 所有线程共享 | 存储运行时动态分配的数据(对象、数组) |
| 方法区(Method Area) | 所有线程共享 | 存储 JVM 读取的每个类/接口的运行时常量池、字段与方法代码、静态变量、方法字节码 |
下面逐一展开。
4.1 PC 寄存器(线程私有)
记录该线程接下来应该执行哪一条指令的部分,保存的是 JVM 指令的地址。每个线程都有自己独立的 PC 寄存器,这是实现线程上下文切换后能恢复执行位置的基础。
4.2 JVM 栈(线程私有)
保存局部变量、方法参数、方法信息、临时数据等。每一个方法调用都会对应一个栈帧(Stack Frame),方法执行完即弹出。它与操作系统线程栈的概念一一对应——仓库内的 Java 中的 Thread 曾指出,调用start()时 JVM 会为线程新建独立的调用栈(Call Stack),而直接调用run()只是复用了main线程的调用栈,并不是真正的多线程执行,这正是面试中常问的"为什么要用start()而不是run()"的答案。
4.3 本地方法栈(线程私有)
用于执行以真实可执行的机器码(Native Code)编写的程序的区域,比如调用 C/C++ 编写的 JNI(Java Native Interface)方法时使用。
4.4 堆(Heap,所有线程共享)
存储运行时动态分配的数据:对象和数组的创建就在这里完成。堆中分配的数据同时也是垃圾回收器(Garbage Collector)回收的对象,因此堆是JVM 性能问题中被提及最多的空间(如 OOM、GC 停顿大多与堆相关)。仓库的 数据结构 Heap 与code/目录下的 MaxHeap.java、MinHeap.java 讨论的是"堆数据结构",而 JVM 中的"堆内存"是一种内存区域划分,两者概念不同,面试时注意区分。
4.5 方法区(Method Area,所有线程共享)
JVM 启动时创建,保存JVM 读取到的每个类与接口的运行时常量池(Runtime Constant Pool)、字段与方法代码、静态变量、方法的字节码等元信息。在 Java 8 之前,方法区的经典实现是永久代(Permanent Generation, PermGen);Java 8 起将其移除,改为使用Metaspace——由 OS 自动调节的原生内存(Native Memory)区域,不再由 JVM 固定大小决定,详见 Java major feature changes 中记录的 Java 8 变更项。
五、执行引擎(Execution Engine):解释器与 JIT 编译器
字节码被加载进内存后,由执行引擎按指令为单位逐一取出并执行。执行引擎采用两种方式:
- 解释器(Interpreter):一条一条地读取字节码指令并解释执行。单条指令执行快,但整体执行速度较慢——因为每条指令都要重复解释;
- JIT 编译器(Just-In-Time Compiler):为弥补解释器的缺点而引入。它将整个字节码(或热点方法)一次性编译为二进制机器码,之后不再对该方法进行解释,而是直接执行编译好的二进制码。由于省去了逐条解释的开销,整体执行速度明显快于纯解释方式。这也是 JVM 能达到接近原生编译语言性能的关键手段。
补充记忆点:现代 HotSpot JVM 通常采用"先解释执行收集热点,再对热点代码做 JIT 编译"的混合模式,配合逃逸分析、内联缓存等优化,进一步提升执行效率。该部分内容同样源自 Java 编译过程。
六、垃圾回收(Garbage Collection, GC):JVM 自动化的内存管理
6.1 背景:从手动管理到自动管理
在 Java 之前,程序员必须手动管理全部程序内存(如 C 语言中的malloc/free)。而在 Java 中,由 JVM 统一管理程序内存,这一职责就是通过垃圾回收(GC)完成的:GC 持续找出 Java 程序中不再被使用(不再被引用)的内存并加以清除。
6.2 执行顺序
① 扫描并删除不再被引用的对象 ↓ ② 归还被删除对象所占的内存 ↓ ③ 复用堆内存(供新对象分配)这条"标记(Mark)→ 清除(Sweep)→ 内存返还与复用"的主线,是所有分代收集器(如 Parallel GC、G1GC)的共同基础逻辑。
6.3 GC 与线程、性能的关系
GC 主要在堆上发生,且会在某些阶段触发全局停顿(Stop-The-World),因此是 JVM 调优和性能分析的核心话题。仓库内相关延伸材料还包括:
- Java 中的 Thread:多线程共享进程内资源时,JVM 需要线程同步(
synchronized、wait/notify、Intrinsic Lock 等),这与 GC 期间线程协调密切相关; - Java major feature changes:记录了 JVM 默认 GC 的演进脉络——Java 8 默认从Serial GC变为Parallel GC(多线程方式),Java 9 起默认变为G1GC(适合多处理器环境),可用于理解 GC 选型的历史背景。
七、JVM 知识点速查表(面试自查)
| 问题 | 答案要点 |
|---|---|
| JVM 是什么? | 管理内存、为 Java 应用提供可移植运行环境的程序规范,也是实际运行的进程;在 Java 与 OS 间充当中间人 |
| JVM 的两大功能? | ① 跨设备/OS 运行 Java 程序 ② 程序内存的管理与优化 |
.java如何变成可执行? | javac 编译为.class字节码 → 类加载器动态加载链接 → 执行引擎解释/JIT → 在运行时数据区域执行 |
| 类加载器五阶段? | 加载 → 验证 → 准备 → 解析 → 初始化 |
| 运行时数据区域有几个? | 5 个:PC 寄存器、JVM 栈、本地方法栈、堆、方法区;其中堆与方法区为所有线程共享 |
| 堆的特点? | 存放对象与数组;GC 的主要目标;JVM 性能问题的高发区 |
| 方法区的变迁? | Java 8 起 PermGen 被移除,改为由 OS 自动调节的 Metaspace |
| 解释器 vs JIT? | 解释器逐条解释慢;JIT 将整体字节码编译为机器码后直接执行,整体更快 |
| GC 做什么? | 持续查找并删除不再被引用的对象 → 返还内存 → 复用堆内存 |
| GC 演进? | Serial → Parallel(Java 8 默认)→ G1GC(Java 9 默认) |
八、结语与仓库延伸阅读
JVM 是 Java 技术栈的基石:类加载器决定了字节码如何进入内存,五大运行时数据区域决定了数据存放的位置与共享边界,执行引擎决定了运行效率,而 GC 决定了内存能否被安全回收。无论你是准备技术面试的新人,还是在排查线上 OOM / GC 问题的工程师,上述五条主线都值得完整掌握。
本文所属的 tech-interview-for-developer 仓库围绕 JVM 还提供了丰富的姊妹篇,建议按以下顺序串读:
- Java 编译过程:从源码到字节码再到执行引擎的完整链路,与本文二、三、五章互补;
- Java major feature changes:Java 8/9/10/11 的 JVM 相关变更(Metaspace、默认 GC 演进);
- Interned String in Java:用代码示例验证常量池(方法区中运行时常量池的一部分)的对象复用行为;
- Java 中的 Thread:从 JVM 调用栈(JVM 栈)与线程同步角度理解多线程在 JVM 中的落地;
- Intrinsic Lock:JVM 线程同步(monitor lock)的内存语义与可见性保障;
- Memory:从操作系统层理解内存管理、MMU、分页等底层机制,可与 JVM 内存管理对照学习。
- 教程
- 知识库
【免费下载链接】tech-interview-for-developer
👶🏻 신입 개발자 전공 지식 & 기술 면접 백과사전 📖
相关推荐
JCSprout JVM内存管理:从类加载到垃圾回收
JCSprout JVM内存管理:从类加载到垃圾回收 本文深入探讨了JVM内存管理的核心机制,涵盖了从类加载到垃圾回收的全过程。首先详细解析了双亲委派模型的工作
文档知识库后端教程深入理解JVM内存分配与垃圾回收机制
深入理解JVM内存分配与垃圾回收机制 前言 Java虚拟机 JVM 的内存管理机制是Java程序员必须掌握的核心知识之一。本文将基于项目中的内存分配与回收策略文
文档教程猫抓浏览器扩展:三步搞定网页视频音频资源下载的终极指南
猫抓浏览器扩展:三步搞定网页视频音频资源下载的终极指南 猫抓(cat catch)是一款功能强大的浏览器资源嗅探扩展,能够智能识别并下载网页中的视频、音频等媒体
音视频
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考