system.arraycopy 直接调用 jvm 底层内存搬运引擎,绕过字节码循环,根据参数动态选择最优机器码路径(如 avx-512),支持安全重叠拷贝,并在数组长度≥64时显著快于for循环。

System.arraycopy 不走 Java 字节码循环路径,它压根不生成 aload、astore、iadd、if_icmpge 这类指令——从调用那一刻起,就跳出了字节码解释器和 JIT 编译器的常规方法处理流程,直连 JVM 底层内存搬运引擎。
它根本不在字节码层面“循环”
手动 for 拷贝在字节码里体现为重复的 aaload 和 aastore(对象数组)或 iaload/iastore(int[]),每次都要:
- 检查索引是否
- 检查索引是否
- 对引用类型额外做 null 判定 + 运行时类型兼容校验
- 执行栈帧跳转、循环变量自增、条件判断等控制流开销
而 System.arraycopy 的字节码只有一条 invokestatic 指令,之后立刻进入 native 层。JVM 不会为它生成任何循环字节码,参数合法性检查也只做一次——通过即搬,失败即抛异常,中间无分支、无迭代、无解释。
JVM 如何把 arraycopy 编译成“硬件级搬运”
HotSpot 并不把它当普通 native 方法对待,而是做了深度内建(intrinsic)优化:
- 小数组(≤32 字节):直接展开为几条 mov 指令,甚至内联进调用方方法体
- 中等数组(64–8KB):调用平台汇编 stub,如 x86 上用
rep movsq(一次搬 8 字节),AArch64 上用ldp/stp成对加载存储 - 大数组(>8KB):自动启用预取(
prefetchnta)、按 64 字节 cache line 对齐、并行分块(多 CPU 核协同搬运)、甚至调用 AVX-512 向量指令(单指令搬 64 字节)
这些不是 Java 代码能控制的——你传入的只是五个整数参数,JVM 根据它们动态选择最优机器码路径。你写 arraycopy(arr,0,dest,0,1024),最终执行的可能是 128 条 movdqu,也可能是 1 条 vmovdqa64,全由运行时环境决定。
为什么重叠拷贝(同数组移位)也安全
当 src == dest 且区间重叠(如左移清理头、右移腾空间),JVM 不依赖你手动判断方向,而是根据 srcPos 和 destPos 关系自动切换策略:
- 若
srcPos (右移):倒序搬运(类似 <code>memmove),从末尾开始写,避免覆盖未读数据 - 若
srcPos > destPos(左移):正向搬运,从开头开始读写,利用已读完区域作临时缓冲 - 若
srcPos == destPos:直接返回,零操作
这和手写 for 循环完全不同——你必须自己加 if 分支、改 i 的增减方向、小心维护临界索引;而 arraycopy 把语义交给 JVM,它在汇编 stub 里就固化了双路径逻辑,无需 Java 层参与。
实测效率分水岭:何时真正快起来
快不是绝对的,是相对 for 循环的“收益净额”。关键看三组数字:
- 长度阈值:基本类型数组 ≥ 64 元素时,arraycopy 开始稳定胜出;≥ 1024 时,速度可达 for 循环的 3–7 倍
- 类型影响:byte[]、int[]、long[] 等能触发 SIMD 指令,最快;Object[] 因需更新 GC 卡表(write barrier),优势缩小约 30%
- 调用频次:单次大拷贝收益高;高频小拷贝(如每毫秒调 100 次 length=4)反而因 JNI 进出开销拖慢整体吞吐
例如:拷贝 1MB 的 byte[],arraycopy 通常耗时 1–2ms;同等 for 循环在未被 JIT 充分优化时可能达 40–60ms,且随 GC 压力波动明显。











