DirectByteBuffer本身不绕过拷贝,仅提供内核可访问的稳定内存地址,使零拷贝成为可能;真正实现绕过依赖transferTo等API触发sendfile/splice等系统调用。

DirectByteBuffer 本身不绕过拷贝,它只是让“绕过成为可能”——关键在后续的 I/O 调用是否走 direct path。
根本限制:JVM 堆内存对内核不可见
HeapByteBuffer 底层是 byte[],分配在 Java 堆中。而 JVM 的 GC 会移动对象(如 Young GC 中 Eden → Survivor),导致堆内地址随时变化。内核执行 DMA 或系统调用时,需要一个稳定、固定、OS 可直接寻址的物理/虚拟地址。堆内存不满足这个前提,所以必须先拷贝到堆外临时缓冲区,再交由内核处理。
DirectByteBuffer 提供了内核可访问的内存基址
它通过 Unsafe.allocateMemory 向操作系统申请一块 native memory(通常为虚拟内存,经 mmap 或 malloc 分配),这块内存:
- 地址长期稳定,不受 GC 影响
- 被映射进 JVM 进程的用户空间,同时对内核可见(可被 sendfile/splice 等系统调用直接引用)
- Java 层通过 address 字段 + Unsafe 操作实现零拷贝读写(如 getLong(address))
真正绕过拷贝的是系统调用,不是 DirectByteBuffer 本身
是否发生“内核态 ↔ 用户态”的二次拷贝,取决于你用什么 API 发起 I/O:
- channel.read(buffer):即使 buffer 是 DirectByteBuffer,仍需内核把数据从 Page Cache 拷贝到该 buffer 地址 —— 这是一次拷贝(kernel → user),但跳过了 heap copy,比 HeapByteBuffer 少一次
- fileChannel.transferTo(0, count, socketChannel):Linux 下若底层触发 sendfile(2),数据可由 DMA 直接从 Page Cache 送入网卡,全程不经过用户态内存 —— 此时才实现真正零拷贝(0 次用户态参与)
- socketChannel.write(directBuffer):若内核支持且路径未降级(如非虚拟化环境、JDK 版本 ≥ 8u231),可能走 splice 或 sendfile,避免用户态中转
常见误区与注意事项
isDirect() 返回 true ≠ 自动零拷贝。实际行为还受以下因素影响:
- JDK 实现版本(旧版可能 fallback 到 heap copy)
- 操作系统支持(如 Windows 不支持 sendfile,transferTo 会退化为普通 read/write)
- 运行环境(某些容器或虚拟化平台屏蔽或拦截了零拷贝系统调用)
- buffer 使用方式(若调用 buffer.get()/put() 手动读写,纯用户态操作,和内核无关;只有 channel 方法才触发 I/O 路径)

















