bigywater.tech
Java · 191

Files.mmap:内存映射文件如何工作

内存映射文件是怎么工作的?什么时候快、有什么限制?这篇用 1 GB 的文件实测 RSS 的变化、随机读速度、映射与普通读写的可见性和边界(Direct ByteBuffer 与堆外内存在前面的篇目)。

44 → 1079 MB
映射 1 GB 文件后的 RSS:map() 之后 → 碰遍所有页之后
15 vs 1003 ns
随机读 8 字节:映射 vs RandomAccessFile(每次)
Size exceeds Integer.MAX_VALUE
一次映射超过 2 GB 的异常

测试文件

实测 · 本机执行输出
  已生成 1024 MB 的文件

惰性

实测 · 本机执行输出
  映射 1 GB 文件前 RSS = 40 MB,map() 之后 RSS = 44 MB(只是建立了地址映射,没有读文件)
  每隔 64 KB 读 1 个字节(只碰到 1/16 的页)之后 RSS = 301 MB
  每页都碰一次之后 RSS = 1079 MB(整个文件进了进程的驻留内存统计)

速度

实测 · 本机执行输出
  顺序读 1 GB(逐字节求和):通过映射 645 ms;FileChannel.read(1 MB 缓冲)后逐字节 718 ms
  随机读 1048576 个 8 字节:通过映射 getLong 16 ms(15 ns/次);RandomAccessFile.seek+readFully 1052 ms(1003 ns/次)
  (文件在操作系统页缓存里,没有测冷读盘)

可见性

实测 · 本机执行输出
  通过映射写入 m.put(0, 77)、m.putInt(100, 123456),没有调用 force():另一条路径 Files.readAllBytes 读到 [0] = 77,[100..103] 的 int = 123456(同一个页缓存,立刻可见)
  再通过 FileChannel.write 在 5 号位置写 99:映射里 m.get(5) = 99(两个方向都互相可见)
  第二个映射写 m2.put(200, 55):第一个映射里 m.get(200) = 55
  force() 之后:数据被要求写回磁盘(是否真的落盘本篇没有验证掉电场景)
  越过映射范围写 m.put(5000):IndexOutOfBoundsException
  READ_ONLY 映射写入 → ReadOnlyBufferException

限制

实测 · 本机执行输出
  READ_ONLY 映射 3 GB(超过 Integer.MAX_VALUE):IllegalArgumentException: Size exceeds Integer.MAX_VALUE
  READ_ONLY 映射超出文件大小的范围:IOException: Channel not open for writing - cannot extend file to required size
  文件 100 字节,READ_WRITE 映射 1 MB:映射之后文件大小 100 → 1048576(READ_WRITE 映射会把文件扩到映射范围)
  映射着的文件被 Files.delete 之后:映射仍然可读 m.get(0) = 0,文件已存在吗:false(这是 macOS 上的结果)

分步演示

内存映射文件
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,macOS,文件在页缓存里
    • 没有验证:冷读磁盘的速度、force() 后掉电是否真的落盘、MemorySegment 映射(JDK 22+ 的 FFM)、Linux 与 Windows 上的行为(包括删除映射中的文件、MAP_PRIVATE 的 PRIVATE 模式)、缺页的耗时分布、大文件的多段映射;RSS 增长比碰到的页多的原因没有查
    京ICP备2026067502号-1京公网安备11010502063487号