连载中 18/20

JIT 与逃逸分析:你的代码不是按你写的样子跑的

2026-09-19 · 5 阅读 · 0 评论 · 0 赞

解释器与 JIT 的接力

Java 启动时靠解释器逐条执行字节码(启动快,跑得慢),代码跑热了由 JIT 编译器翻译成机器码缓存起来(编译要等,跑得飞快)。现代 JVM 用分层编译:C1 快速出活(简单优化),C2 深度优化(编译慢但代码快),冷方法 C1 顶上,热方法升级 C2。热点探测靠计数器:方法调用计数、循环回边计数超阈值就触发编译——循环体里的代码也会被编译(OSR,栈上替换)。

第一刀:方法内联

最重要的优化没有之一。调用方法有压栈开销,JIT 把小方法的方法体直接复制进调用处,调用开销归零,还给后续优化打开了大门(不内联的话,优化跨不过方法边界)。

private int add(int a, int b) { return a + b; }

int sum = add(1, 2) + add(3, 4);
// JIT 内联后等价于:
int sum = 1 + 2 + 3 + 4;
// 再经过常量折叠:int sum = 10;   —— 原调用彻底消失
// 启示:getter/setter 这类小方法大胆用,JIT 会买单

第二刀:锁消除

局部对象如果被逃逸分析证明不会逃出当前线程,它身上的同步就是摆设,JIT 直接拆掉。StringBuffer 是经典例子:方法内 new StringBuffer() 拼字符串,它每个方法都 synchronized,但这个对象只有当前线程摸得到——锁消除后同步开销归零。"局部变量用非线程安全类也没事"的底气,一半来自这里(另一半来自栈上分配,见下)。

第三刀:逃逸分析与标量替换

逃逸分析判断对象的作用范围:不逃逸(只在本方法用)、方法逃逸(当返回值/参数传出去)、线程逃逸(赋给静态变量或跨线程)。不逃逸的对象待遇最好——标量替换:干脆不创建这个对象,把它的字段拆成几个局部变量(标量)放在栈上,连分配和 GC 都省了。这就是为什么"Java 一切皆对象、全在堆上"这句话并不严格成立——大量小对象在 JIT 眼里根本没存在过。

微基准为什么骗人

手写循环测性能,测出来的常常不是代码,而是 JIT 的手术成果:死代码消除(结果没被使用,整个计算被删掉,测了个寂寞)、没预热(前几万次是解释执行,拉低均值)、常量折叠(编译期就算完)。正确姿势是用 JMH:自动预热、防死代码消除、统计置信区间。排查 JIT 行为可用 -XX:+PrintCompilation 看编译日志、JITWatch 做可视化。下一篇讲 -Xmx 管不到的那部分内存——堆外内存。

☕
503

10 年全栈工程师 · 503咖啡馆主理人

#JIT#逃逸分析#方法内联#锁消除#JMH

评论 (0)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10101 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9873 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9294 阅读 · 21 评论 · 287 赞