逃逸分析、标量替换与栈上分配
更新时间:2026-09-18。FAQ 短答见 什么是逃逸分析,本篇补充逃逸判定细节、优化成立条件和验证方法。
一、逃逸的三种程度
| 程度 | 对象的去向 | 能否优化 |
|---|---|---|
| 全局逃逸 | 赋给静态字段、作为返回值传出、被其他线程拿到 | 不能 |
| 方法逃逸 | 作为参数/返回值离开当前方法 | 基本不能 |
| 未逃逸 | 只在方法体内使用,外部拿不到引用 | 可标量替换/锁消除 |
常见逃逸途径:return obj、赋值给堆中字段/静态变量、传给会保存它的方法。
二、三种优化
- 标量替换:未逃逸对象能拆成基本字段(标量)时,JIT 干脆不创建对象,字段直接放栈/寄存器。
- 栈上分配(效果):被替换的对象随栈帧弹出自动回收,不进 Eden、不产生 GC。
- 同步消除:证明对象不会被其他线程访问,就删掉其上的
synchronized。
java
public int sum() {
Point p = new Point(1, 2); // 未逃逸
return p.x + p.y; // JIT 后等价于 return 1 + 2;,对象被消除
}三、重要澄清
HotSpot 没有实现把整个对象放到栈帧上的「真正栈上分配」,它做的是标量替换:只有能拆成标量的对象才免进堆,拆不开的未逃逸对象仍在堆上。文档/面试里说的「栈上分配」在 HotSpot 语境指的就是这个效果。
bash
-XX:+DoEscapeAnalysis # 逃逸分析,JDK8 默认开
-XX:+EliminateAllocations # 标量替换,默认开
-XX:+EliminateLocks # 锁消除,默认开四、优化成立的前提(容易踩)
- 只在 JIT 编译后生效:解释执行不做逃逸分析,微基准必须充分预热(用 JMH),否则测出来「对象都在堆上」。
- 方法不能太大/太复杂:超过内联预算、无法内联时,逃逸信息断了,分析会失效——小方法、
final/短调用链更友好。 - 对象无法被拆分就不享受:大对象、含无法展开引用的对象仍进堆。
五、对编码的指导
- 放心使用作用域局部、不可变的小对象(临时 DTO、Optional、包装类),现代 JIT 会消除,不要为省 new 而手工对象池(对象池反而让对象逃逸、损害缓存局部性)。
- 真正的优化目标是「让短命对象保持短命、不外泄」,而不是减少 new 的次数。
对象默认在 Eden 分配及晋升见 JVM 运行时内存与堆分代;GC 排查见 Full GC 频繁怎么排查。
总结
逃逸分析由 JIT 判断对象是方法逃逸、线程逃逸还是未逃逸,对未逃逸的小对象做标量替换(拆成基本字段放栈/寄存器、等价栈上分配、不进堆不产生 GC)和同步消除(删掉不可能被争用的锁);要注意 HotSpot 并无真正的整体栈上分配、落地靠标量替换,且这些优化只在 C2 编译且方法可内联、预热之后生效,所以应当让短命对象保持局部不外泄、放心用小对象,而不是手工对象池去“优化”本会被消除的分配。