MappedByteBuffer 底层内存不会随 FileChannel 关闭自动释放,需通过反射调用 Cleaner.clean() 或 FileChannelImpl.unmap 显式清理,否则导致文件锁定或越界异常;推荐分段映射、及时清理及封装为 AutoCloseable。

MappedByteBuffer 的底层内存不会随 FileChannel 关闭自动释放,这是 Java NIO 中一个长期存在的资源管理盲点。它本质是操作系统 mmap 的封装,生命周期由 native 层控制,GC 无法触发 munmap。不显式清理会导致 Windows 下文件被锁定(无法删除/重命名),Linux/macOS 上出现 stale mapping(访问越界抛异常),甚至耗尽虚拟地址空间。
为什么 close() 和置 null 都不管用
FileChannel.close()、RandomAccessFile.close() 或将 MappedByteBuffer 设为 null,都只断开 Java 层引用,不影响已建立的 mmap 区域。JVM 不提供 public unmap 方法,其内部 Cleaner 在 JDK 8 中是 sun.misc.Cleaner,JDK 9+ 改为 jdk.internal.ref.Cleaner,且均未暴露给开发者直接调用。
JDK 9+ 推荐:通过 Cleaner 显式清理
利用反射获取 Cleaner 实例并强制清理,是目前最稳定的方式:
- 获取 MappedByteBuffer 关联的 Cleaner 对象:
Field cleanerField = MappedByteBuffer.class.getDeclaredField("cleaner"); cleanerField.setAccessible(true); Cleaner cleaner = (Cleaner) cleanerField.get(buffer); - 调用 clean() 方法:
cleaner.clean(); - 注意:需在 buffer 使用完毕后立即执行,不要依赖 GC;该操作不可逆,之后再访问 buffer 会抛 IOException
JDK 8 兼容方案:反射调用 FileChannelImpl.unmap
适用于老版本项目,但存在兼容风险(不同 JDK 实现类名可能不同):
立即学习“Java免费学习笔记(深入)”;
- 定位私有方法:
Method unmap = FileChannelImpl.class.getDeclaredMethod("unmap", MappedByteBuffer.class); unmap.setAccessible(true); - 执行清理:
unmap.invoke(null, buffer); - 需捕获 Exception 并做好 fallback 处理,例如记录警告日志
更健壮的设计策略
比“事后清理”更可靠的是从源头规避风险:
- 避免长期持有 MappedByteBuffer 实例,用完即清,不缓存、不跨方法传递
- 对超大文件采用分段映射(如每次映射 64MB),处理完一段立刻清理,降低单次映射压力
- 统一管理映射生命周期,例如封装成 AutoCloseable 资源类,在 try-with-resources 中自动清理
- 非强一致性场景下,优先考虑 transferTo/transferFrom,它们不依赖 mmap,无释放难题


















