1.4 Java程序的运行机制
高级语言跑起来只有两种方式:编译型(整本翻译好再跑)和解释型(现场边翻边跑)。Java 的答案是两头都占:先用 javac 编译成字节码,再由 JVM 解释执行,跑得多的代码 JIT 直接编译成机器码缓存——这就是"一次编译、到处运行"背后的完整机制。
但它是第一章最重要的一节:1.1 只说了结论(先编译再解释),这节把 JVM 拿到字节码之后发生的事拆开讲透。以后面试问"Java 是编译型还是解释型",答案就在这节。
1. 先看两大流派:编译型 vs 解释型
回忆计算机基础 05:CPU 只认机器码,高级语言必须翻译。翻译的时机有两种:
| 编译型(C / C++ / Go) | 解释型(Python / JavaScript) | |
|---|---|---|
| 翻译方式 | 运行前整本翻好,产出机器码文件(.exe) | 运行时现场翻译,读一句执行一句 |
| 速度 | 快(跑的全是母语机器码) | 慢一些(边翻边跑) |
| 跨平台 | 差——Windows 编译的 .exe,Mac 听不懂,换平台要重新编译 | 好——有解释器(翻译官)就能跑 |
| 发给朋友 | 发 .exe | 发源码,对方得装解释器 |
一句话:编译型快但不跨平台,解释型跨平台但不快。 C 和 Python 各占一头。
2. Java 的答案:先编译,后解释
Java 不站队,它把两个动作串起来:
HelloWorld.java --javac 整本编译--> HelloWorld.class(字节码)
↓
各平台的 JVM 现场翻译执行
- 第一步(编译):javac 把源码一次性翻成字节码——注意字节码不是任何平台的机器码,它是 JVM 专属的"中间语"
- 第二步(解释):JVM 把字节码现场翻译成对应平台的机器码执行
好处两头占:编译保证发布的是统一的字节码(跨平台);解释保证字节码能在任何平台跑起来。这就是 1.1 篇"混合派"的完整版说法。
问:"Java 是编译型还是解释型?" 答:"严格说都不是——它是先编译成字节码,再解释执行的混合机制。字节码面向 JVM 而不是 CPU,跨平台靠的是各平台各装一个 JVM。"
3. JVM 拿到字节码后的四站(本节核心)
java HelloWorld 敲下回车后,JVM 内部按顺序走四站:
① 类加载(ClassLoader)
把 HelloWorld.class 从硬盘搬进内存。按需加载——用到哪个类加载哪个,不是一口气全搬。
② 字节码校验(安检) 检查字节码有没有越界访问、伪造指针等危险操作。这一站是 Java "安全"名声的来源之一:字节码可能是从网上下载来的,先安检再执行。
③ 执行引擎:解释器 + JIT 双引擎
- 解释器:读一句翻一句执行一句,启动快
- JIT 即时编译器(Just-In-Time):发现同一段代码被反复执行(比如循环里的代码,行话叫"热点代码"),就整段编译成机器码缓存起来,下次直接跑机器码
两个引擎配合:起步靠解释器(不冷场),热点靠 JIT(越跑越快)。这就是"Java 程序跑一段时间后性能会上升"的原因。
④ 运行时内存区 代码执行时的数据和对象都住在这,主要三个房间:
| 房间 | 住什么 | 谁管 |
|---|---|---|
| 堆(Heap) | new 出来的对象(大头) |
GC 打扫 |
| 栈(Stack) | 方法调用、局部变量(后进先出,像摞盘子) | 方法结束自动清 |
| 方法区 | 类的信息、常量 | 类卸载时清 |
06 篇讲过 CPU 的"书签"(下一条指令在哪)。JVM 里每个线程都有自己的程序计数器,记着"当前线程执行到字节码第几条"——理论课的书签在这正式上岗。
4. GC:Java 帮你倒垃圾
C/C++ 程序员写代码要手动申请、手动释放内存,忘了释放就"内存泄漏"——程序越跑越肥,最后撑死。
Java 的解法:GC(垃圾回收器)自动盯着堆内存,发现哪个对象没人用了(没有任何引用指它),就定期收走。
如果一个没用的对象还被某个集合攥着不放(比如一直往 List 里 add 却从不 remove),GC 认为"还有人用它",就不收——引用还在,垃圾就不算垃圾。所以 Java 也有内存泄漏,只是形式不同。这个话题学到集合(第四章后)会再碰。
System.gc() 只是"建议"GC 干活,它听不听随缘。初学阶段记住:写代码不用管内存回收,但要别攥着没用的对象不放。
5. 常见误区
不是。机器码是给特定 CPU 看的(x86 和 ARM 都不一样);字节码是给 JVM 看的中间语,平台无关。把字节码直接喂给 CPU,它一脸懵。
放在 20 年前对。有了 JIT 后,热点代码会被编译成机器码缓存,跑得越久越接近纯编译型的速度——服务器上的 Java 程序长期运行时性能非常能打,这是它能守住企业后端的关键。
跑 .class 只需要 JRE(JVM + 类库),不需要 javac 编译器。但开发者日常直接装 JDK(JDK ⊃ JRE ⊃ JVM,1.1 篇的结论)。
不是,按需加载:main 里用到谁,加载谁。这也是为什么几 MB 的程序启动时只加载一部分类。
恰恰相反,JIT 让热点代码越跑越快。程序越跑越慢一般是内存泄漏(误区 4 的反例:攥着对象不放)或数据量变大,不是运行机制的锅。
6. 本节自测
翻译的时机:编译型在运行前整本翻成机器码;解释型在运行时现场逐句翻。前者快但不跨平台,后者跨平台但慢。
它面向 JVM 而不是 CPU:所有平台上的字节码完全一样(一次编译),平台差异全被 JVM 吸收(各装各的翻译官)。少了字节码这一环,就得像 C 一样每个平台重新编译。
JIT 把被反复执行的热点字节码整段编译成机器码并缓存。首次走解释器(慢),跑热之后直接执行机器码(快),长期运行的程序整体性能接近编译型。
还有引用指着它(比如一直往 List 里 add 不清理)。GC 的判断标准是"有没有人引用",不是"逻辑上还需要"。攥着不放就等于不给 GC 打扫的机会。
堆(Heap)。GC 负责在对象失去所有引用后回收;栈上的局部变量则随方法结束自动出栈,不走 GC。
7. 下一步学什么
- ☐ 1.5 IntelliJ IDEA开发工具 —— 告别记事本和黑窗口,装上程序员真正的日常工具
- ☐ 第二章 变量与数据类型 —— 让程序不光会喊 Hello,还会算账
8. 相关笔记
- 1.3 第一个Java程序 ← 上一篇:亲手走了编译和运行两步
- 1.1 Java概述 ← 字节码与 JVM 的初次登场
- 计算机基础 05 ← 编译型/解释型完整讲解
- 计算机基础 06 CPU ← 程序计数器(书签)上岗
- ☕ Java基础 · 索引
- 🏠 返回主页