Skip to content
困难

一句话答案

JIT 将热点代码(调用超过阈值)编译为本地机器码,关键优化:方法内联、逃逸分析(栈上分配/标量替换/锁消除)。

核心要点

为什么需要 JIT(Just-In-Time)编译:

Java 代码的执行过程:
  .java → javac 编译 → .class(字节码)→ JVM 解释执行

问题:解释执行逐条翻译字节码为机器码,速度慢

JIT 的作用:
  在运行时将"热点代码"(频繁执行的代码)直接编译为本地机器码
  后续执行直接运行机器码,跳过解释过程 → 速度接近 C/C++

热点探测(Hot Spot Detection):

JVM 通过计数器判断代码是否是"热点":

① 方法调用计数器:统计方法被调用的次数
   默认阈值:Client 模式 1500 次 / Server 模式 10000 次
   超过阈值 → 触发 JIT 编译

② 回边计数器:统计循环体执行的次数
   循环体执行次数超过阈值 → 触发 OSR(On-Stack Replacement,栈上替换)
   即:循环执行到一半,切换为编译后的机器码继续执行

C1 和 C2 编译器(分层编译):

维度C1(Client Compiler)C2(Server Compiler)
优化程度轻量级优化(编译快)重量级优化(编译慢,但代码更快)
编译速度快(毫秒级)慢(可能几十到几百毫秒)
优化手段方法内联、常量折叠、简单逃逸分析全部 C1 优化 + 标量替换、循环展开、向量化等
适用阶段程序启动期(快速编译,尽快变快)程序稳态期(充分优化,达到最快)

分层编译(Tiered Compilation,JDK 8+ 默认开启):

Level 0: 解释执行(收集 profile 数据)
Level 1: C1 编译,不带 profiling
Level 2: C1 编译,带有限的 profiling
Level 3: C1 编译,带完整的 profiling(为 C2 收集数据)
Level 4: C2 编译(最高优化级别)

典型路径:L0 → L3(C1 + profiling)→ L4(C2 深度优化)

逆优化(Deoptimization):

JIT 编译基于运行时的假设进行激进优化:
  例:"这个方法只有一个实现类" → 内联该实现

如果假设被打破(如动态加载了新的实现类):
  → JIT 必须"逆优化",退回到解释执行
  → 重新收集 profile 数据,再次编译

典型触发场景:
  ① 新类加载导致虚方法表变化
  ② 分支预测假设被推翻
  ③ 数组越界等异常路径被触发
追问与易错

追问方向:

  • 什么是热点代码?怎么判定?
  • 逃逸分析失败的场景?
  • C1 和 C2 编译器的区别?

易错点:

  • ❌ 所有对象都能栈上分配——必须通过逃逸分析判定不逃逸
  • ❌ 混淆 JIT 编译优化和代码优化

💡 记忆锚点

JIT像翻译官升级:先逐句口译(解释执行),哪段台词念了上万遍(热点代码)就背下来直接说(编译为机器码)。C1快译粗糙版应急,C2精雕细琢出终版。假设被推翻(新实现类加载)就回退重来(逆优化)。