system.arraycopy不是零拷贝,而是cpu拷贝;真正高效的做法是减少其使用、numa亲和内存分配、线程与内存同节点绑定,使必要拷贝延迟稳定在80–120ns。

System.arraycopy 本身不是零拷贝机制,它本质是 CPU 拷贝(JVM 内部用 rep movsb 或 movq 等指令实现),在用户态内存间搬移数据,无法绕过 CPU、不减少内存带宽占用,也不感知 NUMA 节点。把它和“零拷贝”或“NUMA 近核分配”直接挂钩,属于常见概念误用。
真正能压榨算力的组合,是 避免 arraycopy + 用 NUMA 感知方式分配内存 + 让数据始终留在本地节点。重点不在“如何用 arraycopy 做零拷贝”,而在于“怎样让 arraycopy 尽量少做、且每次做的代价最小”。
以下三点是实操中真正起效的关键路径:
-
规避冗余拷贝才是第一优先级
- 用
ByteBuffer.allocateDirect()配合Unsafe.copyMemory或VarHandle批量操作,比反复arraycopy更可控 - 在序列化/反序列化环节改用 FlatBuffers 或 Cap’n Proto,直接内存映射访问字段,跳过对象构造和字节数组中转
- 接收网络数据时,通过 JNI 绑定 DPDK 或 AF_XDP 的 mbuf,让 payload 指针直接指向预分配的本地 NUMA 页,完全绕开 JVM 堆和
byte[]搬移
- 用
-
内存必须按 NUMA 节点亲和分配
- JVM 层面:启用
-XX:+UseNUMA -XX:NUMAGranularity=2M -XX:NUMALimitPerNode=16G,让 G1 或 ZGC 按节点切分 heap region - 原生层(C++/JNI):用
libnuma的numa_alloc_onnode()或mbind()显式绑定内存到当前 CPU 所属 node;启动时mmap(MAP_HUGETLB | MAP_POPULATE)预占 2MB 大页并锁定物理位置 - 关键效果:
arraycopy即使发生,源和目标都在同一 NUMA node 内存上,延迟稳定在 80–120ns 级别,而非跨 QPI 总线的 300+ns
- JVM 层面:启用
-
让计算线程与内存严格绑定
- 用
pthread_setaffinity_np()或taskset把 Java 应用线程(尤其是 Netty EventLoop、ForkJoinPool 工作线程)绑死到特定 CPU core,并确保该 core 与numa_alloc_onnode()分配的内存同属一个 node - 避免“线程在 node 0,数据在 node 1”的典型反模式——此时哪怕没
arraycopy,仅一次get()字段访问也可能触发远程内存读取
- 用
简单说:不是靠 arraycopy 实现零拷贝,而是靠设计让它几乎不用发生;不是靠 JVM 自动 NUMA 感知,而是靠显式控制把内存、线程、缓存行全部钉在同一个物理域内。这样,每一次真实发生的内存操作,都是低延迟、高带宽、无跨片开销的。
不复杂但容易忽略。











