逃逸分析让不会逃出方法的对象不进堆(标量替换),甚至去掉锁。它到底什么时候生效、什么时候失效?这篇用 ThreadMXBean 的线程分配字节数逐个验证,并用 -XX:-DoEscapeAnalysis 做对照。
[默认(C2 逃逸分析开启)] 局部对象只用来调用 sum()(不逃逸) 每次迭代分配 0.01 字节,每次迭代 0.58 ns 同样的对象,再赋给静态字段(逃逸) 每次迭代分配 24.00 字节,每次迭代 1.97 ns 在方法里创建并返回,调用方立刻用完(内联后不逃逸) 每次迭代分配 0.00 字节,每次迭代 0.58 ns 只有 1/1024 的迭代才赋给静态字段(条件逃逸) 每次迭代分配 24.00 字节,每次迭代 1.39 ns [-XX:-DoEscapeAnalysis] 局部对象只用来调用 sum()(不逃逸) 每次迭代分配 24.00 字节,每次迭代 1.64 ns 同样的对象,再赋给静态字段(逃逸) 每次迭代分配 24.00 字节,每次迭代 1.69 ns 在方法里创建并返回,调用方立刻用完(内联后不逃逸) 每次迭代分配 24.00 字节,每次迭代 1.36 ns 只有 1/1024 的迭代才赋给静态字段(条件逃逸) 每次迭代分配 24.00 字节,每次迭代 1.29 ns
[默认(C2 逃逸分析开启)] 传给一个很大的、不会被内联的方法 每次迭代分配 20.78 字节,每次迭代 9.39 ns new int[8],只读写下标 0 和 7(局部小数组) 每次迭代分配 0.00 字节,每次迭代 0.58 ns new int[100],只读写下标 0 和 99 每次迭代分配 416.00 字节,每次迭代 23.47 ns new int[4 + (i & 3)](长度不是常量) 每次迭代分配 0.02 字节,每次迭代 0.58 ns lambda 捕获一个变量并立刻调用 每次迭代分配 0.00 字节,每次迭代 0.31 ns 遍历一个 3 元素 List 的 for-each(迭代器) 每次迭代分配 32.00 字节,每次迭代 6.13 ns Optional.of(...).map(...).orElse(...) 每次迭代分配 32.00 字节,每次迭代 2.77 ns [-XX:-DoEscapeAnalysis] 传给一个很大的、不会被内联的方法 每次迭代分配 24.00 字节,每次迭代 6.22 ns new int[8],只读写下标 0 和 7(局部小数组) 每次迭代分配 0.02 字节,每次迭代 0.58 ns new int[100],只读写下标 0 和 99 每次迭代分配 416.00 字节,每次迭代 33.13 ns new int[4 + (i & 3)](长度不是常量) 每次迭代分配 0.02 字节,每次迭代 0.61 ns lambda 捕获一个变量并立刻调用 每次迭代分配 16.00 字节,每次迭代 1.11 ns 遍历一个 3 元素 List 的 for-each(迭代器) 每次迭代分配 32.00 字节,每次迭代 3.96 ns Optional.of(...).map(...).orElse(...) 每次迭代分配 32.00 字节,每次迭代 3.30 ns
[默认(C2 逃逸分析开启)] Integer.valueOf(i + 1000)(超出缓存范围)只用于求和 每次迭代分配 0.01 字节,每次迭代 0.58 ns Integer 放进 ArrayList 再取出 每次迭代分配 16.00 字节,每次迭代 3.22 ns StringBuilder 局部 append 后取 length 每次迭代分配 0.02 字节,每次迭代 0.03 ns "ab" + int 字符串拼接后取 length 每次迭代分配 0.02 字节,每次迭代 0.03 ns synchronized 在局部 new Object() 上 每次迭代分配 0.01 字节,每次迭代 0.59 ns [-XX:-DoEscapeAnalysis] Integer.valueOf(i + 1000)(超出缓存范围)只用于求和 每次迭代分配 16.00 字节,每次迭代 2.93 ns Integer 放进 ArrayList 再取出 每次迭代分配 16.00 字节,每次迭代 5.07 ns StringBuilder 局部 append 后取 length 每次迭代分配 56.00 字节,每次迭代 3.33 ns "ab" + int 字符串拼接后取 length 每次迭代分配 48.00 字节,每次迭代 2.99 ns synchronized 在局部 new Object() 上 每次迭代分配 16.00 字节,每次迭代 6.40 ns