Java直接内存回收不经过堆GC,依赖Cleaner在DirectByteBuffer被回收时触发Unsafe.freeMemory();可通过GC日志中PhantomReference清理、Cleaner跟踪日志、NMT统计及JMX BufferPool监控间接观察。

Java 中直接内存(Direct Memory)的回收不经过堆 GC,而是依赖 sun.misc.Cleaner 或 java.lang.ref.Cleaner(JDK 9+)在对象被垃圾回收时触发清理逻辑。因此,GC 日志本身不直接记录直接内存的分配与释放细节,但可通过间接方式监控其回收行为——关键在于观察与直接内存生命周期强相关的对象(如 DirectByteBuffer)的回收情况,以及结合 JVM 参数暴露底层清理行为。
启用详细 GC 日志并关注 ByteBuffer 相关对象回收
DirectByteBuffer 实例本身位于堆上,其持有的 native 内存通过 Cleaner 关联。当该对象被 GC 回收时,Cleaner 会被入队并由守护线程处理,最终调用 Unsafe.freeMemory()。因此:
- 开启详细 GC 日志(如
-Xlog:gc*,gc+ref=debug,JDK 11+)或传统参数(-XX:+PrintGCDetails -XX:+PrintReferenceGC),可看到DirectByteBuffer作为“软/虚引用”关联对象被回收的记录; - 特别关注日志中类似
SoftReference、PhantomReference(Cleaner 继承自它)的清理行,例如:
[0.123s][info][gc,ref] SoftReference 1234 refs, 0 enqueued
若某次 GC 后大量PhantomReference入队,可能意味着一批 DirectByteBuffer 正在被清理; - 配合
-XX:+PrintAdaptiveSizePolicy可辅助判断是否因 DirectByteBuffer 堆对象增多导致 GC 频率上升(间接反映直接内存使用压力)。
启用 Cleaner 和 Unsafe 相关调试日志
JVM 提供了底层清理行为的追踪开关:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
-
-Dsun.misc.Cleaner.track = true(JDK 8)或-Djdk.internal.ref.Cleaner.track = true(JDK 9+):启用 Cleaner 跟踪,会在 System.err 输出类似Cleaner registered for java.nio.DirectByteBuffer@12345678和Cleaner cleaned java.nio.DirectByteBuffer@12345678的日志; -
-XX:+PrintNMTStatistics(需先开启 NMT):配合 Native Memory Tracking(-XX:NativeMemoryTracking=detail),可在运行时用jcmd <pid> VM.native_memory summary查看 direct memory 分配总量,对比不同时间点的Internal或Other区域变化,推断回收效果; - 注意:Cleaner 日志默认输出到标准错误流,建议重定向到文件便于分析。
结合 JMX 和工具定位泄漏风险
GC 日志只能反映“已发生”的回收,无法预警泄漏。更实用的方式是主动监控:
立即学习“Java免费学习笔记(深入)”;
- 通过 JMX 访问
java.nio:type=BufferPool,name=directMBean,读取TotalCapacity、Count属性——若长期增长不回落,说明 DirectByteBuffer 未被及时回收(常见于未调用buffer.clear()/clean()或线程未退出导致 Cleaner 线程阻塞); - 使用
jstack <pid>检查Reference Handler和Finalizer(JDK 8)或Cleaner-0(JDK 9+)线程状态,确认其是否卡住(如 blocked on lock); - Arthas 的
vmtool --action getInstances --className java.nio.DirectByteBuffer可实时查看堆内 DirectByteBuffer 实例数,辅助验证 GC 日志中的回收是否真实发生。
避免误判:区分直接内存与元空间、代码缓存等原生区域
GC 日志中出现的 Metaspace、Compressed Class Space 或 CodeHeap 回收,与直接内存无关。直接内存属于 JVM 进程的 native heap,不受 GC 控制,也不在 Metaspace 管理范围内。混淆这两者会导致监控方向错误:
- DirectByteBuffer 分配调用
Unsafe.allocateMemory(),释放靠 Cleaner +Unsafe.freeMemory(); - 而 Metaspace 使用的是 mmap/malloc-like 内存池,由 JVM 自己管理,GC 日志中的
Metaspace GC不代表 direct memory 回收; - 若观察到
OutOfMemoryError: Direct buffer memory,应优先检查-XX:MaxDirectMemorySize设置和 Cleaner 执行是否滞后,而非调整-XX:MaxMetaspaceSize。

















