DirectByteBuffer 的堆外内存不由 JVM GC 直接回收,而是通过 Cleaner 机制异步释放:对象不可达后,GC 将其 Cleaner 入队,ReferenceHandler 线程调用 clean() 执行 Unsafe.freeMemory();buffer = null 仅为必要前提,clear() 无释放作用,长期持有引用将导致内存泄漏。

Java 虚拟机本身不管理直接内存,它属于操作系统内存;ByteBuffer 释放直接内存,并非靠 JVM GC 直接回收,而是依赖 Cleaner 机制自动触发底层 native 内存释放。关键在于:DirectByteBuffer 对象在堆中只是一个“句柄”,真正内存在堆外,释放动作由 Cleaner 关联的 Deallocator 完成。
DirectByteBuffer 的创建与 Cleaner 绑定
调用 ByteBuffer.allocateDirect(size) 时,JVM 会做两件事:
- 通过
Unsafe.allocateMemory()向操作系统申请堆外内存 - 同时创建一个
sun.misc.Cleaner实例,将其与该 DirectByteBuffer 对象绑定 - Cleaner 持有虚引用(PhantomReference)指向该 buffer,并封装一个
Deallocator(实现 Runnable),其run()方法内调用Unsafe.freeMemory(address)
释放的实际触发流程
释放不是“调用某个方法就立刻发生”,而是一套异步、依赖 GC 的链式过程:
- 当 DirectByteBuffer 对象不再被任何强引用持有(例如变量置为
null或超出作用域) - JVM 在某次 GC(尤其是 Full GC)中判定该对象可回收,将其关联的 Cleaner 加入内部的
ReferenceQueue - JVM 后台线程
ReferenceHandler检测到队列中有 Cleaner,自动调用其clean()方法 -
clean()执行Deallocator.run()→ 最终调用Unsafe.freeMemory()归还内存给操作系统
为什么 buffer.clear() 或 buffer = null 不等于释放内存?
这两个操作只是影响 Java 堆中对象的可达性,不直接操作堆外内存:
立即学习“Java免费学习笔记(深入)”;
-
buffer.clear()只是重置 position/limit/mark,对内存无任何释放作用 -
buffer = null是必要前提——它让对象失去强引用,为 GC 回收创造条件,但不保证立即释放 - 若 buffer 被静态集合长期持有(如
static List<bytebuffer></bytebuffer>),GC 永远不会回收它,Cleaner 就永远不会触发,导致直接内存泄漏
生产环境中的实用建议
不能依赖“等 GC”来及时释放大块直接内存,尤其在高吞吐网络服务中:
- 避免长期持有 DirectByteBuffer 引用,使用完尽快置为
null - 合理设置 JVM 参数:
-XX:MaxDirectMemorySize=512m,防止无节制分配导致OutOfMemoryError: Direct buffer memory - 不推荐频繁调用
System.gc(),成本高且不可控;仅在极少数可控场景(如模块卸载)中作为辅助手段 - 如需精准控制,可考虑 Netty 等框架的引用计数机制(
ReferenceCountUtil.release(buffer)),它绕过 Cleaner,主动调用释放逻辑


















