system.arraycopy() 的高性能源于 jit 编译器将其内联为硬件级指令,而非执行 java 代码;需严格满足空检查、索引越界、类型兼容等约束,支持安全重叠拷贝,小数组或需转换时不宜使用。

System.arraycopy() 实现高性能片段搬运,核心不在“怎么写”,而在“它根本没执行你写的那行 Java 代码”——JIT 编译器在运行时直接把它替换成 CPU 指令。理解这点,才能真正用好它。
片段搬运的正确写法与关键约束
搬运不是语法糖,是精确的内存操作,五个参数必须严丝合缝:
- src 和 dest 都不能为空,否则抛 NullPointerException
- srcPos ≥ 0 且 srcPos + length ≤ src.length;destPos ≥ 0 且 destPos + length ≤ dest.length,越界立即抛 ArrayIndexOutOfBoundsException
-
类型兼容性不可妥协:int[] 不能拷到 long[];String[] 可拷到 Object[],但反过来不行;泛型擦除不影响判断,List
[] 拷到 Object[] 是合法的 - length = 0 是合法且无开销的,适合做空校验或占位逻辑
重叠搬运(如数组内平移)的安全机制
System.arraycopy 支持源与目标为同一数组且区间重叠,语义等价于 memmove 而非 memcpy:
- 当 destPos > srcPos(向右位移),JVM 自动从高下标往低下标复制,避免覆盖未读数据
- 当 destPos (向左位移),自动正序拷贝,确保安全
- 例如把 arr[5]~arr[9](共 5 个元素)右移 3 位到 arr[8]~arr[12],只需:
System.arraycopy(arr, 5, arr, 8, 5); - 它不检查“你想干什么”,只按你传的坐标执行——方向错误、长度算错、越界,全由你负责
JIT 内联与硬件级优化的真实原理
它的快,不是因为“native”,而是因为 HotSpot C2 编译器在热方法编译阶段,查 intrinsic 表后彻底跳过 Java 层调用栈,生成高度定制的机器码:
- 边界检查只做一次,在入口处完成所有合法性校验(空数组、索引、长度、类型),后续全是裸指针访存
- x86-64 上可能展开为 rep movsq(对齐的 8 字节块搬)、或带 movaps/movdqu 的 SIMD 循环;AArch64 上常用 ldp/stp 成对加载存储,SVE 向量指令在支持时自动启用
- 若源/目标地址自然对齐(如 16/32/64 字节),JIT 会自动选用更宽向量单元(AVX-512 下单次搬 64 字节)
- 插入硬件预取(prefetch)指令,提升缓存命中率;手写循环需靠 Unsafe.prefetchRead,且效果难控
- @IntrinsicCandidate 注解只是“报名表”,是否生效取决于 JVM 版本、C2 是否启用、CPU 特性检测结果(如 -XX:+UseAVX 未开启则不用 AVX 指令)
性能对比与使用边界
实测中差异显著,但优势有前提:
- 对 int[] 拷贝 100 万个元素(JDK 17 / Linux x86_64):
• System.arraycopy:约 0.03–0.05 ms
• 手写 for 循环(局部变量缓存):约 0.12–0.18 ms
• 增强 for 循环:约 0.25–0.40 ms(迭代器开销+频繁边界检查) - 优势随数组增大而放大;byte[] 差距更明显,可逼近 memcpy 极限
- 小数组(≤ 4 元素)或需条件过滤/转换时,arraycopy 不适用;反复调用它拷 1 个元素反而因 JNI 开销得不偿失
- 对象数组搬运涉及 GC 写屏障(尤其跨代引用),性能优势弱于基本类型;此时浅拷贝语义也需开发者自行管理生命周期











