MappedByteBuffer 是 Java 提供的基于操作系统虚拟内存映射(MMAP)技术的文件读写 API,底层不再通过 read、write、seek 等系统调用实现文件的读写。

我们需要通过 FileChannel#map 方法将文件的一个区域映射到内存中 ,代码如下。

public class MappedByteBufferStu{  
  @Test  
  public void testMappedByteBuffer() throws IOException {  
      FileChannel fileChannel = FileChannel.open(Paths.get(URI.create("file:/tmp/test/test.log")),  
                StandardOpenOption.WRITE, StandardOpenOption.READ);  
      MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 4096);  
      fileChannel.close();  
      mappedByteBuffer.position(1024);  
      mappedByteBuffer.putLong(10000L);  
      mappedByteBuffer.force();  
  }  
}  

上面代码的功能是通过 FileChannel 将文件(0~4096)区域映射到内存中,
调用 FileChannel 的 map 方法返回 MappedByteBuffer,在映射之后关闭通道,随后在指定位置处写入一个8字节的 long 类型整数,
最后调用 force 方法将写入数据从内存写回磁盘(刷盘)。

映射一旦建立了,就不依赖于用于创建它的文件通道,因此在创建 MappedByteBuffer 之后我们就可以关闭通道了,对映射的有效性没有影响。

实际上将文件映射到内存比通过 read、write 系统调用方法读取或写入几十 KB 的数据要昂贵,从性能的角度来看,MappedByteBuffer 适合用于将大文件映射到内存中,如上百 M、上 GB 的大文件。

FileChannel 的 map 方法有三个参数:

MapMode:映射模式,可取值有 READ_ONLY(只读映射)、READ_WRITE(读写映射)、PRIVATE(私有映射) ,READ_ONLY 只支持读,READ_WRITE 支持读写,而 PRIVATE 只支持在内存中修改,不会写回磁盘;
position 和 size:映射区域,可以是整个文件,也可以是文件的某一部分,单位为字节。
需要注意的是,如果 FileChannel 是只读模式,那么 map 方法的映射模式就不能指定为 READ_WRITE。如果文件是刚刚创建的,只要映射成功,文件的大小就会变成(0+position+size)。

通过MappedByteBuffer读取数据示例如下:

public class MappedByteBufferStu{  
    @Test  
    public void testMappedByteBufferOnlyRead() throws IOException {  
        FileChannel fileChannel = FileChannel.open(Paths.get(URI.create("file:/tmp/test/test.log")),  
                    StandardOpenOption.READ);  
        MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, 4096);  
        fileChannel.close();  
        mappedByteBuffer.position(1024);  
        long value = mappedByteBuffer.getLong();  
        System.out.println(value);  
    }  
}  

mmap 绕过了 read、write 系统函数调用,绕过了一次数据从内核空间到用户空间的拷贝,即实现 零拷贝 - 磁盘消息文件的读取,MappedByteBuffer 使用直接内存而非 JVM 的堆内存。

mmap 只是在 虚拟内存 分配了地址空间,只有在第一次访问虚拟内存的时候才分配物理内存。在 mmap 之后,并没有将文件内容加载到物理页上,而是在虚拟内存中分配地址空间,当进程在访问这段地址时,通过查找页表,发现虚拟内存对应的页没有在物理内存中缓存则产生缺页中断,由内核的缺页异常处理程序处理,将文件对应内容以页为单位(4096)加载到物理内存中。

由于物理内存是有限的,mmap 在写入数据超过物理内存时,操作系统会进行页置换,根据淘汰算法,将需要淘汰的页置换成所需的新页,所以 mmap 对应的内存是可以被淘汰的,被淘汰的内存页如果是脏页(有过写操作修改页内容),则操作系统会先将数据回写磁盘再淘汰该页。

数据写过程如下:

  1. 将需要写入的数据写到对应的虚拟内存地址;
  2. 若对应的虚拟内存地址未对应物理内存,则产生缺页中断,由内核加载页数据到物理内存;
  3. 数据被写入到虚拟内存对应的物理内存;
  4. 在发生页淘汰或刷盘时由操作系统将脏页回写到磁盘。