逃逸分析与标量替换
逃逸分析是 JIT 编译器的重要优化。它分析对象是否会逃出当前方法或线程,从而决定是否可以做更激进的优化。
# 1. 什么是逃逸
对象如果被外部方法、外部线程或全局变量访问,就称为逃逸。
没有逃逸:
public int sum() {
Point p = new Point(1, 2);
return p.x + p.y;
}
p 只在方法内部使用,没有逃出方法。
发生逃逸:
public Point create() {
return new Point(1, 2);
}
对象被返回给调用方,逃出了当前方法。
# 2. 逃逸级别
常见理解:
- 不逃逸:对象只在方法内部使用。
- 方法逃逸:对象逃出当前方法,但不一定被其他线程访问。
- 线程逃逸:对象可能被其他线程访问。
逃逸程度越低,JIT 可做的优化越多。
# 3. 标量替换
如果对象不逃逸,JIT 可能不真正分配对象,而是把对象字段拆成局部变量。
Point p = new Point(1, 2);
return p.x + p.y;
可能被优化为类似:
int x = 1;
int y = 2;
return x + y;
这就是标量替换。
# 4. 栈上分配
很多资料会说逃逸分析可以支持栈上分配。更准确地说,HotSpot 更常见的优化结果是标量替换,最终对象可能不再实际分配。
不要简单理解成所有不逃逸对象都真的分配在线程栈上。
# 5. 同步消除
如果一个对象不会被其他线程访问,那么对它加锁没有意义,JIT 可能消除锁。
public void method() {
Object lock = new Object();
synchronized (lock) {
// do something
}
}
lock 没有线程逃逸,锁可能被消除。
# 6. 对性能的影响
逃逸分析可以减少:
- 堆对象分配。
- GC 压力。
- 锁开销。
但开发者不应该为了“帮助逃逸分析”写难懂代码。清晰建模和合理对象生命周期更重要。
# 7. 阶段小结
逃逸分析让 JVM 判断对象是否真的需要在堆上分配、是否真的需要锁。标量替换和同步消除是它的重要优化结果。理解它可以帮助我们解释为什么有些短生命周期对象并没有想象中那么昂贵。
# 8. 逃逸分析流程
发现 new 对象
│
▼
分析引用流向
│
├─ 只在方法内部使用
│ └─ 不逃逸 -> 可标量替换 / 锁消除
│
├─ 返回给调用方或赋给外部对象
│ └─ 方法逃逸 -> 优化受限
│
└─ 存入静态字段或被其他线程访问
└─ 线程逃逸 -> 需要完整对象语义
# 9. 标量替换示例
源码:
class Point {
int x;
int y;
}
int compute() {
Point p = new Point();
p.x = 1;
p.y = 2;
return p.x + p.y;
}
优化后可以近似理解为:
int compute() {
int x = 1;
int y = 2;
return x + y;
}
这样不仅省掉对象分配,也减少 GC 压力。
# 10. 哪些写法会导致逃逸
| 写法 | 是否逃逸 |
|---|---|
| 只在方法内部使用 | 通常不逃逸 |
| return 返回对象 | 方法逃逸 |
| 赋值给成员变量 | 方法逃逸或线程逃逸 |
| 赋值给静态变量 | 线程逃逸 |
| 放入共享集合 | 线程逃逸 |
| 传给未知方法 | 可能逃逸 |
# 11. Tips 快问快答
Q:逃逸分析一定会优化成功吗?
A:不一定。它依赖 JIT、代码形态、运行时信息和优化预算。
Q:为了逃逸分析要不要写奇怪代码?
A:不要。保持代码清晰,热点路径通过 profiling 再针对性优化。
Q:对象没有逃逸就一定分配在栈上吗?
A:不严谨。HotSpot 更常见的是标量替换,使对象不实际分配。
Q:逃逸分析和锁有什么关系?
A:如果锁对象不会被其他线程访问,JIT 可能消除无意义的同步。
上次更新: 2026/06/24, 16:03:01