文档目录

Kotlin/JVM 性能基础

本章定位:本章不教工具,只解决一个问题——为什么你的测量结果可能是假的。学完你才有资格谈「优化」。

前置知识:第 0 章(0.9 从 C++ 到 JVM) 预计总时长:约 4 小时(阅读 2.5 小时 + Lab 2 约 1.5 小时) 配套代码:docs/code/02-jvm-performance-basics/


一、本章要回答的七个问题

  1. 为什么「同一个方法」在前 100 次调用和第一万次调用时,性能能差 5 倍?(第 1 节)
  2. 为什么代码里加一种「类型」就可能让性能掉一半?(第 2 节)
  3. 为什么你写的基准测试测出了「0.2 纳秒」这种物理上不可能的数字?(第 3 节)
  4. 怎么用数据(而不是拍脑袋)判断「预热够了」?(第 4 节)
  5. 为什么「堆越大越好」是错的?该看哪个指标?(第 5 节)
  6. 加了 8 个线程,吞吐反而下降了,为什么?(第 6 节)
  7. 为什么把数据库查询放进协程里,整个服务会一起卡住?(第 7 节)

二、为什么这一章排在所有工具前面

第 0 章讲了观念,但观念需要机制来支撑。举三个例子:

第 0 章的结论 本章补上的机制
「必须预热」 分层编译:解释执行 → C1 → C2,每一级阈值不同(第 1 节)
「不共享优于聪明地共享」 上下文切换、伪共享、安全点的真实成本(第 6 节)
「协程便宜但有限」 Dispatchers.Default 的并行度 = CPU 核数,阻塞调用会饿死它(第 7 节)

如果先学工具(k6、火焰图),你会用它们产出一堆漂亮的假数据,并在错误的方向上投入大量时间。 顺序不能反。


三、小节地图

节 标题 一句话内容 时长 要动手
1 四种执行形态:为什么必须预热 解释执行 → C1 → C2,性能随调用次数台阶式提升 25 min ✅ 实验
2 JIT 的三大优化与去优化 内联、逃逸分析、以及「假设被打破」的代价 25 min ✅ 实验
3 微基准四大陷阱 DCE、常量折叠、分支预测、未消费结果 30 min ✅ 实验
4 预热、稳态与冷启动 同一份代码在三个时间尺度上是三个系统 25 min ✅ 实验
5 内存与 GC:分配速率才是关键 「堆有多大」不如「每秒产生多少垃圾」 30 min ✅ 实验
6 并发原语成本与伪共享 锁、CAS、上下文切换的量级;加线程为何变慢 25 min ✅ 实验
7 协程的真实开销模型 调度器有上限;阻塞调用是头号杀手 30 min ✅ 实验
8 JMH 与 kotlinx-benchmark 标准化基准工具,以及怎么读它的输出 30 min ✅ 配置
9 Lab 2:三份基准与一个坏基准 亲手对比三种测量方式的可信度 90 min ✅ 实验

四、本章的产出物

  1. 三份基准:裸计时 / JMH / kotlinx-benchmark,对同一个算法做测量,并解释数字差异。
  2. 一个坏基准:故意构造被 JIT 优化掉的基准,并解释它为什么假。
  3. 一张「并发成本表」:无竞争锁、CAS、线程创建、上下文切换、协程挂起,各自是你自己机器上测出的量级。
  4. 一次调度器饥饿实验:观察到「阻塞调用污染 Default 调度器」的全过程。

五、学完本章的验收标准

  • 能解释为什么「第一次调用」和「第一万次调用」不是同一段代码。
  • 能说出「去优化」是什么,以及什么样的代码容易触发它。
  • 能识别一个基准是否踩了四大陷阱,并说出怎么修。
  • 能用数据(不是感觉)判断稳态是否到达。
  • 能说出 JVM 上比「堆占用」更重要的内存指标是什么。
  • 能解释「线程越多吞吐越高」为什么是错的。
  • 能说出 Dispatchers.Default 和 Dispatchers.IO 的并行度上限,以及阻塞调用的后果。

六、一个提醒

本章的代码都是可以运行的,但请记住:

它们的数字来自你的开发机,有后台进程干扰、没有 fork 隔离、没有严格预热。这些数字只用于量级对比和机制验证,不能用于任何工程决策。

真正可信的基准从第 8 节(JMH)开始。前面 7 节的价值在于:让你在读到 JMH 的规则时,知道每一条规则在防什么。