System.arraycopy是native方法,底层调用memmove等C级指令,不经过Java堆分配路径,故无“零分配”参数开关;其零分配特性需通过GC日志、JFR等工具观测无新生代分配事件来反向验证。

不能用 JVM 参数直接“证明” System.arraycopy 是零分配,但可以通过组合参数 + 工具观测来验证它在特定场景下确实不触发对象分配 —— 关键在于排除 GC 日志中的新生代分配记录,并确认无额外数组或包装对象生成。
为什么 JVM 参数本身不直接显示“零分配”
System.arraycopy 是 native 方法,底层调用 C 级内存拷贝(如 memmove),整个过程不经过 Java 堆的对象创建路径。JVM 没有专门标记“本次调用分配了 0 字节”的日志开关;它的“零分配”是通过“没出现对应分配行为”反向确认的。
实操验证步骤:用 GC 日志和 JFR 观察
在可控测试中拼接两个 int[],确保目标数组已预先分配好:
- 启动参数加
-XX:+PrintGCDetails -Xlog:gc+allocation=debug(JDK 11+)或-XX:+PrintGCTimeStamps -XX:+PrintGCDetails(旧版),重点关注每次 arraycopy 前后是否出现Allocation request或Young GC触发 - 用
jcmd <pid> VM.native_memory summary</pid>对比 arraycopy 前后 “internal” 和 “malloc” 区域变化 —— 若无明显增长,说明未调用 new/malloc 分配新缓冲区 - 开启 JDK Flight Recorder:
-XX:+FlightRecorder -XX:StartFlightRecording=duration=60s,filename=recording.jfr,settings=profile,回放时筛选Object Allocation In New TLAB事件,执行 arraycopy 的线程应无该事件
必须满足的前提条件
只有满足以下全部,才能观察到真正的零分配效果:
- 源和目标都是基本类型数组(如
int[]、byte[]),避免引用类型数组引发的隐式引用写入开销 - 目标数组长度已精确预分配,且 ≥ 所有要复制段的总长度
- 不混用泛型集合转换(如
list.toArray()调用前先 new 数组,那分配发生在 toArray 内部,不是 arraycopy 干的) - 避免在循环中反复 new 小数组再 arraycopy —— 那些 new 才是分配源,arraycopy 只是搬运工
对比反例:一眼看出非零分配
如果看到如下现象,说明你没测到真正的 arraycopy 行为:
- GC 日志里紧挨着 arraycopy 调用出现
ParNew或G1 Evacuation Pause—— 很可能你在 copy 前刚 new 了一个大数组 - JFR 显示某次
arraycopy调用后立刻有java.lang.Integer或java.util.ArrayList实例分配 —— 那是业务逻辑在别处分配的,和 arraycopy 无关 - 用
Unsafe.allocateMemory或ByteBuffer.allocateDirect替代了 arraycopy —— 这些本身就是显式分配,不能用来对标
本质上,arraycopy 的零分配不是靠参数开关打开的特性,而是它天然不走 Java 对象分配路径的结果。验证它,靠的是“控制变量 + 观测空白”。

















