directbytebuffer 的内存由操作系统管理,jvm 仅通过 cleaner 异步回收,不保证及时性;频繁分配易致 oom,应复用、池化并避免依赖自动清理。

DirectByteBuffer 的内存到底归谁管
Java 的 ByteBuffer.allocateDirect 分配的不是堆内存,而是操作系统原生内存(通常通过 malloc 或 mmap),JVM 不直接管理它的生命周期——它靠一个隐藏的 Cleaner 关联到虚引用(PhantomReference)来触发回收。这意味着:**你不能靠 GC 时机预测它何时释放,也不能靠 System.gc() 强制回收**。
- 分配时 JVM 会记录已用直接内存大小(受
-XX:MaxDirectMemorySize限制,不设则默认等于-Xmx) - 当分配失败抛出
OutOfMemoryError: Direct buffer memory,往往不是因为没空闲内存,而是因为 Cleaner 还没运行、旧缓冲区还没释放 - 频繁创建/丢弃
DirectByteBuffer容易堆积未清理对象,尤其在 GC 压力小(如老年代长期不触发 Full GC)时更明显
为什么调用 cleaner.clean() 也不一定立刻释放
即使你手动触发 sun.misc.Cleaner(比如反射拿到并调用),也只是把释放逻辑提交进一个单线程的 Cleaner 队列,实际释放由后台线程执行——这个线程可能被阻塞、调度延迟,甚至在某些 JDK 版本中因线程优先级低而“饿死”。
- JDK 9+ 中
Cleaner已迁移到java.lang.ref.Cleaner,但行为类似:异步、无保证顺序、不保证及时性 -
Unsafe.freeMemory()是最终释放系统内存的调用,但它只在Cleaner线程里执行,无法从应用线程同步等待 - 别试图用
ByteBuffer.clear()或compact()来“释放”直接内存——它们只重置 position/limit,对底层内存毫无影响
怎么避免 OOM 和内存泄漏
核心思路是:**少分配、复用、早显式清理(如果真有必要)**。不要依赖自动回收扛住高吞吐场景。
本文档主要讲述的是关于Objective-C手动内存管理的规则;在ios开发中Objective-C 增加了一些新的东西,包括属性和垃圾回收。那么,我们在学习Objective-C之前,最好应该先了解,从前是什么样的,为什么Objective-C 要增加这些支持。有需要的朋友可以下载看看
- 用池化方案:Apache Commons Pool 或 Netty 的
PooledByteBufAllocator,复用DirectByteBuffer实例 - 确认是否真需要 direct:纯内存拷贝、零拷贝网络发送(如
SocketChannel.write(ByteBuffer))才值得;否则堆内ByteBuffer更稳 - 监控关键指标:JVM 启动加
-XX:+PrintGCDetails,观察日志中Direct buffer memory行;或用 JMX 查java.nio:type=BufferPool,name=direct的MemoryUsed - 极端情况可强制触发 Cleaner:调用
System.gc()+ 短暂Thread.sleep(10)(不推荐生产,仅调试),但别写进业务逻辑
JDK 21+ 的变化和现实约束
JDK 21 引入了 ScopedMemoryAccess 和 MemorySegment(属于 Panama 项目),目标是替代 DirectByteBuffer 的手动管理,但目前 ByteBuffer API 仍完全兼容旧逻辑,所有已有代码行为不变。
立即学习“Java免费学习笔记(深入)”;
- 新 API 仍处于 incubating 阶段(
--enable-preview),生产环境慎用 - 即便未来全面迁移,底层仍是
mmap/malloc+Cleaner模式,只是封装更安全——不会改变“非即时释放”这一根本约束 - 真正难处理的从来不是“怎么释放”,而是“什么时候该认为它已释放”:操作系统
free后,内存页未必立刻归还给系统,top或ps看到的 RSS 可能滞后数秒甚至更久









