Java中与“Pinned Memory”语义最接近的是DirectByteBuffer,它分配堆外内存,支持零拷贝、避免GC干扰,是Netty等高性能框架的基石;需通过池化(如PooledByteBufAllocator)安全使用,并严格release以防止堆外内存泄漏。

Java 中没有“Pinned Memory”这个原生概念,那是 CUDA 或某些底层系统(如 Windows Driver Kit)里的术语,指被锁定、不可被操作系统换出物理内存的页。Java 里与之语义最接近、且在高频网络 IO 场景中真正起效的,是 DirectByteBuffer —— 它分配的是堆外内存(Off-Heap),由操作系统直接管理,绕过 JVM 堆,避免 GC 干扰和数据拷贝,是 Netty、Kafka、gRPC 等高性能框架的底层基石。
为什么 DirectByteBuffer 能优化高频网络 IO?
传统堆内 ByteBuffer 在 socket write/read 时需经历:堆内 → 内核缓冲区 → 网卡,中间至少一次 JVM 到内核的 copy(比如通过 System.arraycopy 或 JNI 调用)。而 DirectByteBuffer 的内存地址可被 JNI 直接传递给 native 网络栈(如 epoll/kqueue),实现真正的零拷贝(Zero-Copy)—— 数据从网卡 DMA 直达堆外内存,或反之。实测在万级并发连接、小包高频收发场景下,吞吐量提升 30%~50%,GC 暂停减少 70%+。
如何安全高效地使用 DirectByteBuffer
直接 new DirectByteBuffer(1024) 是危险的:它不走池化、不复用、易触发堆外 OOM,且无自动清理保障。正确做法是依托成熟内存池,例如:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
Netty 的 PooledByteBufAllocator:默认启用池化,自动管理 DirectByteBuffer 的分配、回收、复用。只需配置:
PooledByteBufAllocator.DEFAULT或自定义 arena 大小;发送时用ctx.writeAndFlush(Unpooled.directBuffer().writeBytes(data))即可获得池化 direct buffer。 -
显式控制生命周期:所有从池中获取的 ByteBuf 必须调用
.release()(尤其在 ChannelHandler 中),否则内存不会归还池,导致OutOfMemoryError: Direct buffer memory。建议配合 try-with-resources(若继承了 ReferenceCounted)或使用io.netty.util.ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID)进行泄漏检测。 -
监控堆外内存水位:通过 JMX 检查
java.nio:type=BufferPool,name=direct的TotalCapacity和MemoryUsed;或用jstat -gc <pid>观察OU(Off-Heap Used)列。持续增长且 GC 不回落,即存在泄漏。
常见陷阱与规避方式
DirectByteBuffer 本身不是银弹,误用反而加剧问题:
立即学习“Java免费学习笔记(深入)”;
-
频繁创建未池化 direct buffer:比如在每次 HTTP 请求中
ByteBuffer.allocateDirect(4096),会快速耗尽-XX:MaxDirectMemorySize(默认≈-Xmx)。应统一走池化分配器。 - 忘记释放 + 强引用闭包:在 CompletableFuture 回调中持有对 DirectByteBuffer 的强引用,或将其存入静态 Map,会导致 buffer 及其背后内存长期无法回收。务必 release 后再丢弃引用。
-
JVM 参数未调优:默认
-XX:MaxDirectMemorySize可能过小(如仅 1GB),高并发下需显式加大(如-XX:MaxDirectMemorySize=4g),并与堆大小协调(避免 OS 内存不足)。
替代方案:虚拟线程 + 非阻塞 IO 已成新范式
Java 21+ 的虚拟线程让“每个连接一个线程”回归简洁模型。此时,若配合 NIO(如 SocketChannel.read(ByteBuffer))并使用 DirectByteBuffer,既能享受零拷贝,又无需手动管理线程池。注意:虚拟线程本身不改变 DirectByteBuffer 行为,但它大幅降低了高并发下内存与调度开销,使 DirectByteBuffer 的收益更稳定、更易落地。例如 Spring Boot 3.2 中开启虚拟线程后,WebMvcConfigurer 配合 ServerCustomization 使用 NIO + direct buffer,QPS 提升显著且延迟毛刺减少。


















