bigywater.tech
Java · 175

System.arraycopy:数组复制为什么能被 JVM 特殊优化

System.arraycopy 为什么比手写循环快?它和 Arrays.copyOf、clone 有什么区别?这篇先看语义(重叠、类型检查、异常),再用 JIT 开关把「JVM 特殊优化」量出来(不看 HotSpot 源码,只用实测;JIT 本身见前面一篇)。

0.069 vs 0.177 ns
arraycopy 与手写循环的每元素耗时(100 万个 int)
0.157 vs 7.554 ns
-Xint 下 arraycopy 与手写循环的每元素耗时
[a, b, null, null]
ArrayStoreException 抛出时目标数组已被复制的部分

语义

实测 · 本机执行输出
  System.arraycopy(x, 0, x, 1, 5)(同一数组、区间重叠)→ [1, 1, 2, 3, 4, 5](像先复制到临时数组再写回)
  自己写的从前往后的循环 y[i+1] = y[i] → [1, 1, 1, 1, 1, 1](重叠时被覆盖,整行都变成 1)
  从后往前的循环 → [1, 1, 2, 3, 4, 5]
  Object[]{"a","b",3,"d"} 复制到 String[]:抛 ArrayStoreException: arraycopy: element type mismatch: can not cast one of the elements of java.lang.Object[] to the type of the destination array, java.lang.String;已经复制进去的:[a, b, null, null](失败前的元素已经被复制)
  目标放不下 → ArrayIndexOutOfBoundsException: arraycopy: last destination index 6 out of bounds for int[3];目标数组 = [0, 0, 0](越界检查在复制之前,没有部分复制)
  int[] → long[] → ArrayStoreException: arraycopy: type mismatch: can not copy int[] into long[]
  源是 null → NullPointerException
  复制 0 个元素、起点等于长度:System.arraycopy(new int[3], 3, new int[3], 3, 0) 不抛异常

耗时

实测 · 本机执行输出
  System.arraycopy           0.069 ns/元素(复制 1048576 个 int,后 4 轮取最好)
  手写 for 循环                  0.177 ns/元素(复制 1048576 个 int,后 4 轮取最好)
  Arrays.copyOf              0.077 ns/元素(复制 1048576 个 int,后 4 轮取最好)
  src.clone()                0.072 ns/元素(复制 1048576 个 int,后 4 轮取最好)
  Arrays.copyOfRange(整段)     0.071 ns/元素(复制 1048576 个 int,后 4 轮取最好)

没有 JIT 时

实测 · 本机执行输出
  [默认(有 JIT)]  最后一轮:System.arraycopy 0.079 ns/元素,手写循环 0.173 ns/元素
  [-Xint(纯解释器)]  最后一轮:System.arraycopy 0.157 ns/元素,手写循环 7.554 ns/元素

分步演示

System.arraycopy
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12
    • 没有验证:arraycopy 在 JIT 里被替换成什么(是否 intrinsic、用了哪些指令,本篇没有看反汇编)、小数组(几个元素)的耗时、引用数组复制时的写屏障开销、-XX:-UseSuperWord 对手写循环的影响、JDK 17 与 27;耗时是自写循环的一次运行
    京ICP备2026067502号-1京公网安备11010502063487号