Skip to content

编程语言的发展与分类 ​

S2-002选修入门约 30 分钟JDK 21计算机基础 · 计算机导论

1. 本节定位 ​

《计算机组成与二进制》讲了机器看得懂的东西(二进制);本节讲**"人写的东西怎么变成机器能跑的"**:

text
Java 源码 → 字节码 → (JIT)机器码
   ↑          ↑           ↑
 人看得懂    JVM 看得懂   CPU 看得懂

理解这条链路,才能理解后面要学的:JVM 内存结构、类加载、GC、性能调优(《JVM 内存结构与对象创建》 起)✓

2. 学习目标 ​

  • 说出四代编程语言的演进与各自的抽象点 ★;
  • 能区分编译型、解释型、以及 Java 的"混合模式" ★★★;
  • 知道字节码是什么,以及它为什么带来"一次编写到处运行" ★★★;
  • 能说出 JIT 与 AOT 的取舍 ★★;
  • 知道**"语言选择"在真实项目里该看什么** ✓。

3. 前置知识检查 ​

4. 为什么需要这个知识 ​

先看一个真实困惑 ✗:

text
"Java 是编译型还是解释型语言?"
  答"编译型"—— 那它为什么需要一个 JVM 才能运行? ✗
  答"解释型"—— 那它为什么要先编译成 .class 文件? ✗

两种回答都不完整 ✓ ——Java 是"先编译、后(选择性)编译"的混合模式 ✓✓

★ 为什么这个区分重要:

如果要理解…就得先懂…
为什么 Java 启动慢JIT 需要预热 ★★
为什么服务跑一会儿会变快热点代码被编译成机器码 ★★
为什么有 -Xint / -Xcomp 参数可以强制关掉/打开 JIT ✓
为什么 AOT 能加快启动省掉编译阶段 ✓

5. 核心概念 ​

5.1 四代语言的演进 ★ ​

代代表抽象点人写的是
一代:机器语言打孔纸带无抽象0 和 1 ✗
二代:汇编MOV / ADD / JMP用助记符代替指令寄存器操作 ✓
三代:高级语言C / Java / Python用表达式和结构代替寄存器业务逻辑 ✓✓
四代:领域语言 / 声明式SQL / 正则 / DSL描述"要什么"而不是"怎么做"意图 ✓✓✓

★ 演进的本质是"把复杂度往下推" ✓:

text
汇编程序员要自己管寄存器
C 程序员要自己管内存
Java 程序员只管对象(GC 管内存)
SQL 程序员只描述结果集(优化器管执行计划)

★ 每上一层的代价:

抽象越高,越容易写对,但越难精确控制性能 ✓✓

这就是为什么"追求极致性能"的地方仍然用 C/C++/Rust ✓

5.2 编译型、解释型与混合模式 ★★★ ​

三者的执行链路:

text
① 编译型(C/C++):
   源码 --编译器--> 机器码 --直接执行--> CPU
   ★ 一次编译,运行最快;但换个平台要重新编译 ✗

② 解释型(Python/JS):
   源码 --解释器逐行读--> 执行
   ★ 到哪都能跑;但每行都要"读一遍再执行" ✗

③ 混合型(Java/C#)★★★:
   源码 --javac--> 字节码 --JVM 执行--> 
                                     ├─ 解释执行(前几次)
                                     └─ JIT 编译成机器码(成为热点后)✓✓

★ Java 的关键设计:字节码 ✓✓

字节码带来的好处说明
平台无关 ★★★字节码不面向任何 CPU 指令集 ——JVM 负责翻译 ✓
安全沙箱 ★JVM 可以在执行前校验字节码(不允许越界访问内存)✓
性能优化的空间 ★★JVM 运行时才知道"哪段是热点" ——可以针对性优化 ✓✓

★ 最后一条是 Java 的隐藏优势:

静态编译(C++)在编译期就要决定一切;JIT 可以在运行时看到"实际的数据分布" ✓

所以长期运行的 Java 服务,峰值性能可以逼近 C++ ✓✓

5.3 JIT:从解释到编译 ★★ ​

一段 Java 代码的生命周期:

text
第 1 次调用   → 解释执行(慢,但启动快)✓
第 100 次     → 被标记为"热点" 
第 1000 次    → C1 编译(快速编译,轻度优化)
第 10000 次   → C2 编译(慢速编译,深度优化)★

★ 这解释了三个日常现象 ✓✓:

现象原因
Java 服务"跑一会儿变快" ★★热点方法被 C2 编译了 ✓
压测要"预热" ★★★不预热测到的是"解释执行 + 编译过程" ✗(T2-003 就是这么做的)✓
微基准测试很难写对 ✗JIT 会做常量折叠、死代码消除 ——你测的可能不是你想测的 ✗✗

★ 用两个 JVM 参数亲眼验证(本节的可运行实验):

bash
# 强制只用解释器:观察耗时显著变长
java -Xint -cp cs-basics/target/classes cn.kb.learn.cs.BinaryLesson

# 默认(混合模式)
java -cp cs-basics/target/classes cn.kb.learn.cs.BinaryLesson

★ -Xint 下的差距通常是 3~10 倍 ✓ ——这就是 JIT 的价值 ✓✓

5.4 AOT 与 JIT 的取舍 ★★ ​

JIT(即时编译)★AOT(提前编译)★
编译时机运行时构建时
启动速度慢(要先跑一段)✗快 ✓✓
峰值性能高(能看运行时数据)✓✓略低(看不到运行时数据)✓
内存占用大(编译本身要资源)✗小 ✓
典型传统 Java 服务 ✓GraalVM Native Image、云函数 ✓

★ 这个取舍解释了"为什么云原生时代 Java 被诟病" ✓:

Serverless / 容器扩缩容场景下,JVM 的"启动几秒 + 预热几十秒"是致命的 ✗

所以 GraalVM Native Image、Spring Boot 3 的 AOT 支持才变得重要 ✓(《云原生》)

★ 但不要盲目崇拜 AOT ✗:

场景该选
常驻的高并发服务JIT(峰值性能更重要)✓
短生命周期函数 / CLI 工具AOT(启动速度更重要)✓
需要动态特性(反射、动态代理)JIT ✓(AOT 对反射不友好 ✗)★

★ 最后一条是实践中的关键:

Spring 大量使用反射与动态代理 ——这曾让 AOT 编译很难做 ✓

所以 Spring 6 / Boot 3 才要做"AOT 友好化改造" ✓

5.5 语言选择该看什么 ★ ​

真实项目里选语言,看的不是"哪个最好" ✗ ——而是四个约束 ✓:

约束说明
团队会什么 ★★★招不到人的语言等于没有语言 ✓
生态与库 ★★有没有成熟的框架、驱动、中间件客户端 ✓
运行环境 ★部署环境限制(容器大小、内存、启动时间) ✓
性能要求只有真的"性能敏感"时才考虑它 ⚠️

★ 一条经验:

"性能"往往不是选语言的真正理由 ✗ ——80% 的性能问题出在数据库与架构上,不在语言 ✓✓

本项目的实测就印证过这一点:一个内存里的 Servlet,瓶颈在线程调度与 CPU 竞争(T2-003),而不是语言 ✓

6. 递进示例 ​

6.1 最小示例:看字节码 ​

bash
# 编译
javac Hello.java
# 反汇编:看到 JVM 指令
javap -c Hello.class

你会看到:

text
  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #7    // Field java/lang/System.out
       3: ldc           #13   // String Hello
       5: invokevirtual #15   // Method java/io/PrintStream.println

★ 这就是字节码 ✓ ——不是机器码,而是一套"面向 JVM 的指令集" ✓✓

6.2 业务示例:JIT 的影响 ​

powershell
# 混合模式(默认)
Measure-Command { java -cp cs-basics/target/classes cn.kb.learn.cs.BinaryLesson }

# 纯解释模式
Measure-Command { java -Xint -cp cs-basics/target/classes cn.kb.learn.cs.BinaryLesson }

★ 后者通常慢 3~10 倍 ✓

6.3 反例:把 Java 当"编译型语言"理解 ​

text
"Java 编译完就是机器码了" ✗ —— 编译产物是字节码,还要 JVM 翻译 ✓
"Java 一定要 JVM 才能跑?" —— 不一定,GraalVM 可以编成原生可执行文件 ✓

7. 完整可运行代码与实测 ​

本节无新增实验类——它的实验就是用命令行观察 JIT ✓

做法(复现步骤):

powershell
cd labs
.\mvnw.cmd -pl cs-basics compile

# ① 看字节码结构
javap -c -cp cs-basics/target/classes cn.kb.learn.cs.BinaryLesson | Select-Object -First 20

# ② 对比 JIT 开与关
$t1 = Measure-Command { java -cp cs-basics/target/classes cn.kb.learn.cs.ComplexityLesson }
$t2 = Measure-Command { java -Xint -cp cs-basics/target/classes cn.kb.learn.cs.ComplexityLesson }
"混合模式: $($t1.TotalSeconds) 秒 | 纯解释: $($t2.TotalSeconds) 秒"

★ 预期观察:

模式现象
默认(混合)前面的实验慢(解释执行),后面越来越快(JIT 生效) ★★
-Xint全程都慢,且没有"变快"的过程 ✓

★ 这个对比的价值:它让"JIT 是真实存在的机制"从概念变成你亲眼看到的现象 ✓✓

8. Debug 观察点 ​

  1. javap -c 看任意类的字节码:观察"一条 Java 语句对应多条字节码" ★;
  2. javap -v 看常量池:找到字符串常量与 String 拼接的痕迹 ✓;
  3. 加 -XX:+PrintCompilation 参数:观察哪些方法被 JIT 编译、什么时候编译 ★★★;
  4. 用 -Xint 跑同一个程序:对比总耗时 ★★;
  5. 用 -Xss 调小栈:观察深层递归什么时候栈溢出(《树、堆与哈希表》)✓。

9. 常见错误 ​

#错误说明
1说"Java 是编译型语言" ✗它是混合型(先编译成字节码,再选择性 JIT)✓
2以为 .class 就是机器码 ✗✗是字节码,需要 JVM 翻译 ✓
3做微基准时不预热 ✗✗测到的是解释执行与编译过程(《时间复杂度和空间复杂度》)✗
4以为 AOT 全面优于 JIT ✗常驻服务 JIT 峰值更高 ✓(5.4)
5为"性能"换语言 ✗✗瓶颈通常在数据库与架构 ✓
6用 -Xint 做性能测试 ✗那是关掉 JIT 的调试模式,不代表真实性能 ✓

10. 动手实验 ​

目标:亲眼看到"字节码"与"JIT"两件事。

步骤:

  1. javap -c 看一个类,找到 invokevirtual 与 ldc ✓;
  2. 对比 java 与 java -Xint 的总耗时 ★★;
  3. 加 -XX:+PrintCompilation,观察编译日志(会看到方法名被逐行打印)★;
  4. 写一个"前 10 万次慢、之后快"的程序(如一个循环里先调用冷方法、再反复调用)★★;
  5. 记录结论:JIT 让同一段代码的第二次执行快多少 ✓。

验收方式:能说出"-Xint 下慢了几倍,以及为什么"。

扩展任务:查一查自己常用的 JDK 发行版:

  • 用的是哪个 JIT 实现(HotSpot C2?Graal JIT?)✓;
  • 默认的 GC 是哪个(《垃圾回收算法与收集器》 会展开)✓;
  • 说明:为什么"换 JDK 发行版"可能带来性能差异(提示:不同的 JIT/GC 实现与默认参数)✓

11. 练习题 ​

  1. 判断:Java 源码编译后直接得到机器码。(对 / 错)
  2. 填空:Java 的编译产物是 ______;JVM 对热点代码的处理是 ______。
  3. 简答:为什么"压测之前要预热"?
  4. (代码阅读)
java
long start = System.nanoTime();
for (int i = 0; i < 100; i++) { computeOnce(); }
System.out.println((System.nanoTime() - start) / 1_000_000 + " ms");

这段基准测试有什么问题? 5. (编程)写一个小程序,让同一个方法被调用 1 次 / 1 万次 / 1000 万次,分别记录单次平均耗时,观察 JIT 生效前后的差异。

参考答案(要点)
  1. 错。得到的是字节码 ✓ ——机器码由 JVM 在运行时(JIT)生成 ✓★
  2. 字节码(.class);JIT 编译成机器码 ✓
  3. 因为 JVM 前几次是解释执行,热点代码要调用很多次后才被 JIT 编译 ✓ ——不预热测到的是"解释执行 + 编译开销" ✗(T2-003)★
  4. ① 没有预热(前几次是解释执行)✗;② 循环次数太少(1 次调用的方差掩盖一切)✗; ③ 没有防止死代码消除(JIT 可能把没有副作用的结果直接删掉)✗★
  5. 要点:随调用次数增加,单次平均耗时会明显下降,之后趋于平稳 ✓ ——拐点就是"被 JIT 编译"的时刻 ✓

12. 掌握标准 ​

  • [ ] 能说清 Java 的"编译 + 解释 + JIT"三段式 ★★★;
  • [ ] 知道字节码的作用与"平台无关"的原理 ★★★;
  • [ ] 知道 JIT 需要预热,并知道它对压测的影响 ★★★;
  • [ ] 能说出 AOT 与 JIT 的取舍(启动 vs 峰值)★★;
  • [ ] 知道"性能"通常不是换语言的理由 ★★;
  • [ ] 会用 javap 与 -XX:+PrintCompilation 观察 ✓。

13. 扩展资源 ​

14. 下一步学习 ​

《数据结构:数组、链表、栈与队列》:把"抽象类型"落到真实的耗时上。

后续知识

暂无

进度只保存在本机浏览器,可随时清除

个人学习项目:AI 生成内容均经人工复核,第三方资源仅作索引