MappedByteBuffer处理超大文件需分段映射(如64MB)并显式unmap,避免OutOfMemoryError和虚拟内存泄漏;写入后必须force()刷盘,且不可仅用clear()释放资源。

Java 中用 MappedByteBuffer 处理超大文件(如几 GB 甚至几十 GB 的日志),关键不在“全量映射”,而在于“按需分段映射 + 显式控制生命周期”。直接 map(FileChannel.MapMode.READ_WRITE, 0, file.length()) 很容易触发 OutOfMemoryError: Map failed,这不是 Java 内存不够,而是操作系统虚拟地址空间或内核 mmap 限制被突破。
分段映射:避免单次映射过大
Linux 默认 /proc/sys/vm/max_map_count 通常为 65536,单进程最多创建约 64K 个内存映射区。映射一个 20GB 文件(假设页大小 4KB)理论上需要 500 万个页,远超限制。更实际的做法是:
- 每次只映射固定大小的块,推荐 16MB–128MB(例如 64MB),兼顾性能与资源可控性;
- 写场景下,当当前块写满(
buffer.position() == buffer.limit()),就调用buffer.force()刷盘,再unmap(见下文释放),接着channel.map()新块; - 起始位置按块对齐:第
n块映射position = n * chunkSize,长度取Math.min(chunkSize, remainingFileSize),最后一块可能不足。
正确释放映射资源:防止虚拟内存泄漏
MappedByteBuffer 不受 JVM 垃圾回收自动清理——它背后是 native mmap 区域,GC 只能间接触发 Cleaner,但不保证及时。长期运行服务若频繁映射/弃用,会导致 vm.max_map_count 耗尽、后续映射失败。
- Java 9+ 推荐用反射调用
java.nio.Bits.unmap(MappedByteBuffer)或通过Cleaner显式清理(注意:非 public API,需 try-catch 兜底); - Java 8 及更早,可借助
sun.misc.Cleaner(不推荐生产环境强依赖,但无更好替代); - 务必在切换映射块前完成旧 buffer 的
force()和显式unmap,不能仅靠clear()(它只重置 position/limit,不释放 mmap)。
读写操作与数据一致性保障
映射后操作和普通 ByteBuffer 一致,但有两点必须注意:
立即学习“Java免费学习笔记(深入)”;
- 写入后必须调用
buffer.force(),否则修改可能滞留在 page cache,进程崩溃或系统断电时丢失; - 复用同一 buffer 前调用
buffer.clear()(重置读写位置),否则下次put()可能覆盖错误位置或越界; - 读场景若需确保最新内容(尤其多进程共享日志),可配合
buffer.load()预加载,再用isLoaded()检查,但一般追加日志无需主动 load。
典型写入流程示例(伪代码)
以 64MB 分块、追加写日志为例:
- 打开
FileChannel(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND); - 计算当前文件长度,确定下一个块起始位置;
- 调用
channel.map(READ_WRITE, position, chunkSize); - 循环
buffer.put(...)写入数据,写满则force()→ 显式 unmap → 映射新块; - 写完全部内容后,对最后一块也执行
force()并 unmap。


















