DirectByteBuffer通过allocateDirect申请堆外内存,不占-Xmx但受-XX:MaxDirectMemorySize限制;适用于高频I/O场景,需防泄漏并监控native_memory、JMX BufferPool及应用埋点。

直接分配堆外内存,从allocateDirect开始
调用ByteBuffer.allocateDirect(1024)就完成了一次堆外内存申请。它背后会触发DirectByteBuffer构造、Bits.reserveMemory校验、最终由Unsafe.allocateMemory向操作系统要内存。这块空间不占-Xmx堆空间,但默认受-XX:MaxDirectMemorySize限制(JDK 8起默认等于-Xmx值)。
注意:不是“用了allocateDirect就一定快”,它只在频繁I/O场景下体现优势——比如Netty的Channel读写、RocketMQ消息刷盘、大文件零拷贝传输。如果只是临时存几个int,反而因分配慢、回收延迟而得不偿失。
操作字节变量:读写控制靠position/limit/capacity
DirectByteBuffer和HeapByteBuffer共享同一套游标逻辑:
- capacity:固定值,等于你传入的容量(如1024),创建后不可变
- limit:当前可读/可写边界,put()时不能超过limit,flip()后limit变成之前position
- position:下一次读或写的起始索引,每次put/get自动递增
例如写入一个int和一个double:
buffer.putInt(123); // position += 4
buffer.putDouble(3.14); // position += 8
buffer.flip(); // limit = 12, position = 0
之后调用getInt()和getDouble()就能按顺序取出,无需数组拷贝,数据始终在堆外原地操作。
避免堆外内存泄漏的关键动作
DirectByteBuffer本身是Java对象,存在堆上,受GC管理;但它持有的堆外内存靠Cleaner虚引用异步释放——这个机制有延迟,且依赖GC触发。一旦大量DirectBuffer未被及时回收,就会OOM with "Direct buffer memory"。
实战中必须做到:
- 明确生命周期,用完尽快置为null(尤其在长生命周期对象中持有buffer时)
- 避免在循环中无节制创建,改用缓冲池(如Netty的PooledByteBufAllocator)
- 监控关键指标:
java.nio.Bits.reservedMemory(已保留量)、sun.nio.ch.DirectBuffer.count(活跃DirectBuffer数) - 设置合理上限:
-XX:MaxDirectMemorySize=512m,比默认值更可控
调优离不开可观测:三类核心监控手段
光靠参数不够,得看见真实使用情况:
-
JVM自带工具:jstat -gc
查看 CCSC/CCSU(压缩类空间)无直接帮助,但jcmd能显示“internal”和“direct”两栏,后者即DirectBuffer总用量VM.native_memory summary -
JMX接口:通过
java.nio.BufferPool.directMBean获取totalCapacity、count、memoryUsed三项实时值,可集成到Prometheus - 应用层埋点:在allocateDirect前后记录时间戳与容量,在高负载时段做采样分析,识别突发分配源头
当memoryUsed持续接近MaxDirectMemorySize,或count陡增不回落,就是调优信号。











