
摘要本文深入解析 Java 虚拟机JVM的核心原理揭秘 Java 一次编写到处运行的跨平台奥秘。从编译与运行分离、JVM/JRE/JDK 三者的关系到 JVM 内部构造类加载器、运行时数据区、执行引擎、垃圾回收以及完整的程序执行流程通过生动的比喻和清晰的图示帮助 Java 初学者和开发者构建对 Java 运行时的系统性认知。关键词Java 虚拟机 (JVM), 字节码, 跨平台, 垃圾回收 (GC), Java 运行时环境 (JRE)前几篇文章我们从认识 Java 开始搭好了开发环境还亲手写出了第一个Hello, World!程序。当时我说Java 最大的魅力之一就是一次编写到处运行——你在 Windows 上写的代码拿到 Linux 或 macOS 上照样能跑不需要任何修改。但你有没有想过这到底是怎么做到的为什么 C 语言写的程序不行Java 就可以今天我们就深入底层揭开 JVMJava 虚拟机的神秘面纱。放心我不会讲太多晦涩的理论而是用你能听懂的比喻把这件事讲透。1. 从 Hello World 说起 —— 编译和运行是两个步骤还记得上一篇我们写的HelloWorld.java吗我们在 IDEA 里点了一下绿色的运行按钮程序就跑了。但 IDEA 在背后偷偷做了两件事编译把HelloWorld.java我们看得懂的源代码转换成一种叫做字节码Bytecode的文件HelloWorld.class。运行启动 JVM加载HelloWorld.class然后执行里面的指令。这两个步骤是独立的。你可以手动在命令行里体验一下# 第一步编译javac HelloWorld.java# 第二步运行javaHelloWorld编译后你会看到一个HelloWorld.class文件——它就是 Java 字节码。这个文件不是机器码计算机的 CPU 不能直接执行它。它需要被 JVM 解释或编译成当前平台能识别的机器码才能运行。 打个比方你写了一篇中文演讲稿源代码有人把它翻译成了国际通用语字节码然后到了美国当地翻译把它转成英文讲给美国人听到了日本转成日文讲给日本人听。无论去哪个国家那份国际通用语版本都是同一份——这就是 Java 字节码。2. JVM、JRE、JDK —— 从内部视角再看一遍第二篇我们简单对比过三者的关系今天从 JVM 内部视角再看一遍理解会更深。名称全称包含内容作用JVMJava Virtual Machine类加载器、运行时数据区、执行引擎、GC运行字节码JREJava Runtime EnvironmentJVM 核心类库如System、String等运行 Java 程序不能开发JDKJava Development KitJRE 开发工具javac、jar、jdb等开发并运行 Java 程序简单地说JVM是引擎是运行的核心。JRE是引擎 轮胎 方向盘有了它车能跑但你不能造车。JDK是整车 工厂 修车工具你不仅能开还能造车、修车。JVM、JRE、JDK 的关系这三个缩写是 Java 世界里最基础的概念画个图就清楚了我们在第一篇文章里装的是 JDK 21所以它包含了 JRE 和 JVM。当你运行java命令时实际上是在启动 JVM。3. 跨平台的秘密 —— 字节码 JVM现在我们可以回答那个核心问题了为什么 Java 能跨平台答案就是两个关键设计Java 源代码编译成字节码而不是直接编译成特定平台的机器码。每个平台都有自己的 JVM 实现——Windows 有 Windows 版 JVMLinux 有 Linux 版 JVMmacOS 有 macOS 版 JVM。你写的代码只需要编译成一份字节码然后用哪个平台的 JVM它就会自动把字节码转换成对应平台能执行的机器码。再次打个比方你写了一份国际通用语的文件字节码然后你把它带到美国美国翻译官JVM for Windows把它读给美国人听带到日本日本翻译官JVM for macOS读给日本人听。你只需要带一份文件翻译官们各显神通。这就是“一次编写到处运行”的本质。4. JVM 内部构造 —— 它到底长什么样为了让你更直观地理解 JVM 是怎么工作的我把它的核心组成部分画成一个简单的模型。这里不要求你完全记住有个印象就好------------------------------------------ | JVM 架构简图 | ------------------------------------------ | ① 类加载器Class Loader | | 把 .class 文件加载到内存中 | ------------------------------------------ | ② 运行时数据区Runtime Data Area | | ┌─────────┬─────────┬──────────┐ | | │ 堆 │ 栈 │ 方法区 │ | | │ (对象) │ (局部变量)│ (类信息) │ | | └─────────┴─────────┴──────────┘ | | 实际上还包括程序计数器、本地方法栈等 | ------------------------------------------ | ③ 执行引擎Execution Engine | | 把字节码翻译成机器码并执行 | | (解释器 JIT 编译器) | ------------------------------------------ | ④ 垃圾回收Garbage Collector | | 自动清理不再使用的对象 | ------------------------------------------① 类加载器Class Loader它的工作很简单找到.class文件读取它里面的字节码然后把它加载到内存中。我们写程序时用到的String、System这些类也是由类加载器从 JDK 的核心库中加载进来的。② 运行时数据区Runtime Data Area这里是程序运行时的工作空间主要包含几块堆Heap存放所有通过new创建的对象。它是所有线程共享的也是垃圾回收的主要战场。栈Stack每个线程都有自己的栈用来存放局部变量和方法调用的信息。方法执行时压栈执行完出栈。方法区Method Area存放类的结构信息如类名、方法定义、静态变量等。在 JDK 8 之前它在 JVM 堆中JDK 8 之后移至元空间Metaspace使用本地内存管理。程序计数器、本地方法栈等其他辅助区域初学阶段不必深究。初学者不需要深究每个区域的细节只要知道 JVM 为你的程序划分了不同的仓库来存放不同的东西就行。③ 执行引擎Execution Engine这是 JVM 的大脑。它把字节码一种抽象的指令集转换成当前 CPU 能理解的机器码。早期的 JVM 采用解释执行一条一条地解释速度较慢现在的 JVM 引入了JIT即时编译技术——它会识别出频繁执行的热点代码直接编译成机器码并缓存起来后续直接运行机器码速度大幅提升。④ 垃圾回收Garbage CollectorGC这是我们之前提到的自动内存管理的核心。GC 会周期性扫描堆内存找出那些不再被任何引用指向的对象然后把它们占用的空间回收掉供后续使用。想一下当你写new Student()创建一个学生对象并把它赋给变量s。当s后来被赋值为null时那个学生对象就没有人引用了GC 就会在合适的时机把它清理掉。你不用手动delete或free——Java 替你干了。5. 垃圾回收 —— 你的内存清洁工GC 可能是 JVM 中最智能的部分。它的核心思想是可达性分析从一组称为GC Roots的起点如当前运行中的线程、静态变量等出发沿着引用链标记所有能到达的对象。那些无法被标记到的对象就是不再使用的垃圾会被 GC 回收。现代 JVM 有多种 GC 算法例如G1 GCJDK 9 默认把堆分成多个区域优先回收垃圾最多的区域ZGCJDK 11 引入JDK 21 增强超低延迟适合大内存场景分代式 ZGCJDK 21在 ZGC 基础上引入分代思想进一步提升性能我们以后会有专门的文章讲 GC 调优现在你只需要记住GC 自动帮你管理内存你基本不需要操心对象的释放——这大大减轻了开发者的负担。6. 一个完整的运行流程 —— 从 .java 到屏幕输出现在把整个流程串起来你写的源代码HelloWorld.java ↓ (javac 编译) 字节码文件HelloWorld.class ↓ (java 命令启动 JVM) JVM 类加载器把 HelloWorld.class 加载到内存 ↓ JVM 执行引擎将字节码翻译成机器码 ↓ CPU 执行机器码执行 System.out.println(...) ↓ 控制台输出Hello, World!每一步都清晰可见这也是 Java 的设计理念把底层复杂性封装起来让开发者专注于逻辑。今天的总结今天我们聊了三个核心内容编译与运行是分离的——javac产生字节码java启动 JVM 执行字节码。JVM、JRE、JDK 的关系——层层包含JDK 最大JVM 最小但最核心。跨平台的本质——字节码 各平台的 JVM 实现做到一次编写到处运行。JVM 的核心组件——类加载器、运行时数据区、执行引擎、垃圾回收。GC 帮你自动管理内存你不需要手动释放对象。你现在已经站在了一个更高的视角看待 Java不只是写代码而是理解了代码背后运行的舞台。这个舞台就是 JVM它承载了你的程序的一生。动手试试打开命令行进入你之前HelloWorld.java所在的目录手动执行javac HelloWorld.java和java HelloWorld观察生成的.class文件。用文本编辑器如记事本打开HelloWorld.class你会看到一堆乱码那其实是字节码的二进制表示。虽然看不懂但你能感受到它和原始代码的区别。用javap -c HelloWorld命令查看字节码指令如果你装了 JDK你会看到类似getstatic、ldc、invokevirtual这样的指令。感受一下 Java 底层的样子。下篇开始我们就要正式进入 Java 语法的学习了。第一篇讲的是代码的骨架。我们会详细拆解一个 Java 程序的基本结构包括包、导入、类、方法等。别担心我会带着你一步步掌握。我们下一篇见。 获取本系列示例代码请访问 GitCode。