我学习到的JIT即时编译与机器码缓存失效

一、先理清3个核心灵魂疑问(初学者必懂)

先解决大家最困惑的三个基础问题,筑牢认知根基:

疑问1:JVM的「解释执行」,本质是不是翻译?

答案:是!100%就是逐行翻译+立即执行

Java编译后的.class字节码,是JVM专属的"中间语言",电脑CPU完全看不懂。

JVM解释器的工作特别简单:

逐条读取字节码 → 翻译成当前系统(Windows/Linux)专属的二进制机器码(0/1) → 立刻交给CPU执行。

最大的缺点:翻译一次、丢弃一次。下次再执行同一行代码,需要重新翻译,反复做无用功,性能偏低。

疑问2:JIT即时编译的「优化」,优化的是什么?

答案:对最终的原生机器码做全方位优化,不是简单翻译

解释执行是"无脑直译",不做任何改动;而JIT是JVM的性能优化神器,专门盯热点代码(循环、高频调用的接口、反复执行的方法)。

它不会逐行翻译,而是把一整块高频字节码,一次性批量编译、精简、优化,生成最优版本的本地机器码,核心优化包括:

  • 删掉永远执行不到的无效代码
  • 精简重复计算逻辑、调整CPU指令顺序
  • 取消冗余的类型判断、方法跳转

优化完成后,会把这份最优机器码缓存起来,后续再次执行,直接用缓存,不用重复翻译,性能大幅提升。

疑问3:最终到底是谁执行代码?

答案:只有CPU能执行二进制机器码,JVM只负责翻译、优化、调度

完整层级关系,记死这一条:

Java字节码 →(JVM解释/JIT翻译优化)→ 系统原生机器码 → CPU最终执行

JVM是"翻译官+优化师",CPU是唯一的"最终执行者"。

二、核心重点:JIT机器码缓存,什么时候会失效?

JIT生成的优化机器码,存在JVM专属的代码缓存(CodeCache) 中(内存缓存,重启服务直接清空,不落地硬盘)。

缓存不是永久有效的,满足以下4种场景,会直接失效、被清空,代码退回慢速的「解释执行」模式:

1. 代码缓存内存占满(最常见)

CodeCache有固定内存上限,无法无限存储优化机器码。空间不足时,JVM会自动清理长期未调用的冷门缓存代码,腾出空间给新的热点代码。被清理的代码,下次执行需要重新翻译、重新优化。

2. 类被动态卸载、销毁

动态加载的类、无任何引用的临时类被GC卸载后,依附这个类的JIT优化缓存会彻底失效。

3. 服务重启、进程关闭

缓存是进程级内存缓存,服务一旦重启,所有JIT优化缓存全部清空,程序冷启动后重新积累热点、重新编译优化。

4. 运行时假设崩塌(最核心、最难懂:类型不匹配去优化)

这是业务开发中最容易遇到、也是面试高频考点的场景:代码一行没改、服务没重启、只是运行时流量变了,缓存直接失效

下面用支付业务真实案例,手把手带你吃透全过程。

三、真实业务案例:微信支付上线,触发JIT缓存失效

场景前提:整套代码完全没变、服务未重启、无任何发布更新,纯运行时场景变化。

1. 项目固定代码(提前写好,从未改动)

项目中提前定义了支付接口,以及两个支付实现类:支付宝、微信支付,代码一直存在,从未修改:

csharp 复制代码
// 支付统一接口
public interface PayService {
    void pay();
}

// 实现类A:支付宝支付
public class AlipayService implements PayService {
    @Override
    public void pay() {
        System.out.println("支付宝支付扣款成功");
    }
}

// 实现类B:微信支付(代码早已存在,只是前期没调用)
public class WechatPayService implements PayService {
    @Override
    public void pay() {
        System.out.println("微信支付扣款成功");
    }
}

2. 阶段一:线上只走支付宝,JIT大胆优化缓存

项目上线初期,运营只开放了支付宝支付渠道,线上一万次支付请求,100%都是AlipayService实现

JIT编译器会持续采样监控代码运行状态,发现一个固定规律:

这个支付接口,永远只有A这一个实现类,不存在其他类型。

为了极致性能,JIT做了一个激进优化(投机优化)

直接删掉接口动态判断、动态分发的冗余逻辑,把AlipayService的支付代码直接内联,编译成最优机器码,存入CodeCache缓存。

此时性能拉满:不用判断类型、不用跳转方法,CPU直接执行优化后的机器码。

3. 阶段二:运营放开微信支付,假设直接崩塌

运行一段时间后,运营放开微信支付渠道,同一套代码、同一个服务,开始出现WechatPayService的支付请求。

此时JIT发现:之前的核心假设「接口只有支付宝一种实现」彻底错误!

重点:缓存里的优化机器码,已经硬编码死了支付宝的逻辑,删掉了所有类型判断,根本无法处理微信支付的逻辑。

4. 最终结果:缓存失效、退回解释执行

JVM会立刻执行「去优化(Deoptimization)」操作:

  1. 废弃、清空CodeCache中之前的优化机器码缓存;
  2. 停止高速JIT优化模式;
  3. 代码退回慢速的解释执行模式
  4. 后续每次支付请求,老老实实判断是支付宝还是微信,再执行对应逻辑。

后续如果两种支付流量都很高,JIT会重新采样,生成更保守、兼容双类型的新优化缓存。

四、总结

  1. 解释执行=逐行翻译,慢但稳JIT编译=批量优化缓存,快但需要投机假设
  2. 所有代码最终都由CPU执行0/1机器码,JVM只做翻译和优化;
  3. JIT缓存是内存临时缓存,重启即清空,非永久存储;
  4. 最特殊的缓存失效:代码未变、运行时场景变了,导致JIT投机假设崩塌,主动废弃优化缓存;
  5. JIT优化不是越多越好,过度投机优化,遇到动态场景反而会触发去优化,导致性能波动。

五、前端类比

前端V8引擎执行JS,和JIT逻辑完全一致:

变量长期是数字类型,V8会优化成数字专属机器码缓存;一旦突然赋值为对象,类型假设崩塌,立刻去优化,退回解释执行。

这也是为什么复杂动态JS逻辑,偶尔会出现性能抖动的核心原因。

相关推荐
带刺的坐椅1 小时前
Solon 的 10 种 HTTP 服务器:改一行依赖,换一个引擎
java·solon·jetty·undertow·mcp-server·htttp
犀利豆1 小时前
Claude Code Tools 研究系列(三)ExitPlanMode
人工智能·后端
星栈2 小时前
oh-my-pi工程级AI编码工使用体验
人工智能·后端·agent
xcLeigh2 小时前
Go入门:变量声明的五种方式详解
java·开发语言·golang
AI大模型-小华2 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
Mark_ZP3 小时前
【锁1】Synchronized vs ReentrantLock区别
java
立心者03 小时前
SpringBoot中使用TOTP实现MFA(多因素认证)
java·spring boot·后端
枕星而眠4 小时前
C++ STL Map容器完全指南:从有序红黑树到无序哈希表
java·开发语言
逃逸线LOF4 小时前
Spring配置数据源{连接池}(Druid、c3p0)
java·数据库·spring