标量替换是JVM在JIT编译时自动消除对象分配的优化:当对象不逃逸且字段全为标量或可递归标量化时,其字段直接存入寄存器,对象本身消失。

标量替换不是手动拆对象,而是让 JVM 在运行时自动跳过 new 指令——你写的仍是 new Point(x, y),但 JIT 编译后,x 和 y 可能直接作为两个 int 局部变量存在寄存器中,对象本身彻底消失。
确保对象全程“不逃逸”
逃逸分析要求 JVM 能 100% 证明:该对象的引用没被写入堆、没传给外部方法、没被其他线程可见。哪怕只有一行违规代码,整个方法的标量替换就会被禁用。
- 禁止赋值给实例字段或 static 字段:如
this.p = p;或Cache.HOLDER = p; - 禁止进任何容器:包括
list.add(p)、map.put("k", p)、array[0] = p - 禁止传给非 final 方法:如
System.out.println(p)、String.format("%s", p)、Objects.toString(p) - 禁止在 lambda 中捕获:
stream.map(i -> i.x)中的i当前 HotSpot 版本一律视为潜在逃逸
设计可被分解的纯数据类
标量替换只对字段全为标量(int/long/boolean/double)或可递归标量化的嵌套对象生效。引用类型字段(如 String、List)会立即中断整条链。
- 用
final类 +private final基本类型字段,不继承、不实现接口、不重写toString()/hashCode() - 避免任何 getter 的虚调用干扰:方法体必须极简且可内联,例如
public final int x() { return x; } - 嵌套对象也需满足条件:如
Range(Point lo, Point hi)可能被递归拆解,但只要其中含一个String remark,整个Range就无法标量化
启用并验证 JIT 实际生效
标量替换只在 Server VM 的 C2 编译器对热点方法优化时触发,冷代码或 Client VM 不支持。它不会在解释执行阶段发生。
- 启动参数必须包含:
-XX:+DoEscapeAnalysis -XX:+EliminateAllocations(JDK 8u20 后部分版本默认关闭) - 添加诊断开关:
-XX:+PrintEscapeAnalysis -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions - 关键日志线索:
not escaped(逃逸分析通过)、scalar replaced、EliminateAllocations: true - 反汇编中应看不到对象字段加载指令(如
mov %rax, 0x8(%rbx)),而应看到类似%r11 ← 12345的寄存器直赋
适合高频临时对象的典型场景
这类场景天然贴近标量替换条件:生命周期短、作用域封闭、仅用于中间计算,不暴露引用。
- CSV/JSON 解析循环中的单行数据结构,如
InvoiceItem{int skuId; long qty; double price;} - 坐标计算中的
Point、Rect、Vec3等几何类型 - 时间窗口聚合中的
TimeRange{long start; long end;} - 状态校验中的一次性上下文,如
CheckCtx{int code; boolean valid; long ts;}

















