ByteBuffer.allocateDirect()可申请堆外内存,不占堆空间、适合高频I/O,但需设-XX:MaxDirectMemorySize限制,释放依赖对象不可达后Cleaner异步调用Unsafe.freeMemory,非System.gc()触发。

用 ByteBuffer.allocateDirect() 就能申请堆外内存,它不占堆空间、适合高频 I/O,但不是“一调就快”,得看场景和用法。
怎么申请:一行代码启动堆外分配
调用静态方法即可:
-
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);—— 分配 1KB 堆外内存 - 底层会走
DirectByteBuffer构造、Bits.reserveMemory配额检查、再用Unsafe.allocateMemory向操作系统要内存 - 实际分配大小可能略大于传入值(如页对齐补足),JDK 8 默认按 64B 对齐,JDK 17+ 可能更激进
关键限制:别让堆外内存失控
堆外内存不受 -Xmx 管,但受 JVM 显式参数约束:
-
-XX:MaxDirectMemorySize=256m—— 必须设!默认值等于-Xmx,但物理内存充足时容易误判 - 超过该值会直接抛
OutOfMemoryError: Direct buffer memory,和堆满无关 - 可通过 JMX 的
BufferPool.direct或NativeMemoryTracking(开启-XX:NativeMemoryTracking=detail)实时监控用量
操作数据:靠 position/limit/capacity 控制读写边界
和 HeapByteBuffer 共享同一套游标逻辑,原地操作不拷贝:
-
capacity:固定容量(如 1024),创建后不可变 -
position:下一次读/写位置,putInt()后自动 +4,getDouble()后自动 +8 -
limit:当前有效边界,flip()后设为当前position,用于切换读写模式 - 示例:
buffer.putInt(123).putDouble(3.14).flip();→ 写入 12 字节,之后可顺序getInt()和getDouble()
释放要点:不靠 System.gc(),靠对象不可达 + Cleaner
DirectByteBuffer 的堆外内存不会随 GC 立即释放,但也不是完全“手动”:
- 不能靠
buffer = null;+System.gc()强制回收——Cleaner 是异步队列,GC 不保证触发时机 - 真正释放依赖:ByteBuffer 对象被 GC 回收 → Cleaner 关联的
Deallocator执行Unsafe.freeMemory - 高频创建大 buffer(如每次网络请求 new 1MB)易导致 Cleaner 积压、延迟释放,建议复用或使用池化方案(如 Netty 的
PooledByteBufAllocator)

















