MappedByteBuffer 不是真正快照,因其基于操作系统页表映射而非数据拷贝,读取时按需加载实时磁盘内容,外部修改会直接影响已映射区域,导致逻辑不一致、SIGBUS异常或读取结果不可预测。

使用 MappedByteBuffer 进行“快照式”读取时,它不提供文件内容的逻辑一致性快照。映射一旦建立,缓冲区反映的是底层文件在每次访问时刻的**实时状态**,而非映射创建瞬间的静态副本。
为什么不是真正快照?
内存映射本质是操作系统页表层面的虚拟地址绑定,而非数据拷贝。MappedByteBuffer 读取某位置字节时,若对应文件页尚未加载,会触发缺页中断并从磁盘(或 page cache)按需加载——此时读到的是当前磁盘上该偏移处的最新内容。如果外部进程在你读取过程中覆盖了那段文件区域,后续对该区域的访问就会看到新写入的数据。
这种行为在 Linux 下尤其明显:mmap 默认使用 MAP_SHARED 模式(Java 的 READ_ONLY/READ_WRITE 均对应此语义),任何对文件的修改(如 truncate、overwrite、append)都会直接影响已映射区域的可见内容,前提是 OS 已将变更同步到 page cache 或磁盘。
典型不一致场景
- 边读边写:另一个进程用 FileOutputStream 覆盖文件前半部分,你的 MappedByteBuffer 在读到该区域时会拿到新数据,导致前后数据逻辑错乱(例如结构化日志中 header 和 body 不匹配)
-
文件被截断:外部调用 truncate() 缩小文件大小,而你正通过 buffer.get(index) 访问原末尾附近位置,可能触发 SIGBUS(Linux)或 AccessViolation(Windows),抛出
java.lang.InternalError: a fault occurred in a recent unsafe memory access - 追加写入但未刷新:外部程序 append 写入后未调用 fsync,你的映射可能读到旧长度(因 page cache 尚未更新),也可能读到新内容(取决于 OS 调度和缓存策略),结果不可预测
如何缓解一致性风险
- 只读 + 文件锁定:用 FileChannel.lock(0, Long.MAX_VALUE, true) 获取共享锁,阻止其他进程获得写锁(注意:仅限同一文件系统,且不阻塞无锁写入)
- 复制后再映射:对关键文件,先用 Files.copy() 创建临时副本,再对副本做映射——代价是磁盘空间与拷贝时间,但获得强一致性
- 分块校验 + 重试:对敏感数据段,在读取前后用 MessageDigest 计算局部哈希;若两次不一致,说明期间被修改,可选择跳过、告警或重新映射整块
- 避免长期持有映射:缩短映射生命周期,单次处理尽量快,降低被并发修改的概率窗口
本质上,MappedByteBuffer 是一个高性能的“透明通道”,不是隔离沙箱。它快,也正因为不额外维护一致性契约。需要快照语义的应用,应主动叠加文件锁、副本或应用层版本控制机制。










