什么是MMAP?
MMAP(Memory-mapped file)是一种内存映射机制。用于建立从文件到内存之间的一种映射,将对磁盘的写入映射为对内存的写入,以增加对于文件IO的效率。
在Linux上主要通过mmap()、munmap()、msync()等系统调用来实现。
什么是Page Cache?
Page Cache页缓存是Linux实现MMAP机制的底层关键组件,其将文件分为多个大小为4KB的数据块。
前面说文件传输过程中,将磁盘文件拷贝到内核缓冲区中,这个缓存区实际上就是Page Cache。
PageCache的大小在32位OS中为4K,在64位操作系统中大小为8K。 PageCache有两个特性:
-
缓存 在操作系统中,具有「局部性」,即刚被访问的数据再次被访问的概率更高。 所以Linux中使用Page Cache来缓存最近被访问的数据。所以读取磁盘的时候会有先从Page Cache中读取,如果没有再从磁盘中读取。
-
预读 PageCache的一个很好的特性就是预读,也就是当应用程序发生读操作时,文件系统会提前为应用程序读取的文件加载更多的数据到Page Cache中,这样下一次读取的时候就可以直接命中Page Cache了。(很多地方都运用到了这样的技术,比如说JVM中) 有效的减少了磁盘的访问次数。
在Java NIO中通过Channel.map()方法即可将文件区域映射到内存中实现MMAP机制,其会返回一个MappedByteBuffer对象。这个对象在Linux映射的内存区域实际上就是Page Cache。
FileChannel fileChannel = new RandomAccessFile(new File("/tmpFile"), "rw").getChannel();
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1024 * 4);
零拷贝的限制
- 不适合大文件,因为MMAP在加载的时候是预先加载内存中的空间的,如果映射文件太大就会造成占用过的内存空间。
- 不适合变长文件,因为MMAP在使用的时候需要指定映射文件的大小,一般为内存页的整数倍。
- 磁盘延迟,MMAP是通过缺页中断向磁盘发起真正的IO请求,而具体是何时写入将交由系统来决定,而不是应用程序。
RocketMQ中对零拷贝的优化
- 内存预映射机制 RocketMQ通过维护一个MappedFileQueue队列,在每次消息的写入都获取消息队列中的最后一个MappedFile,如果没有则会创建一个并且将下一个也创建好。
- 文件预热 在调用mmap()进行内存映射时,其实只是建立起虚拟内存与物理地址的映射关系,不会加载任何文件到内存中。 而RocketMQ的在MappedFile#warmMappedFile方法中,每隔一个页(4k)就put一个0,将文件加载到内存中,这样当消息读取或者写入的时候可以直接命中Page Cache。
- mlock内存锁定 在warmMappedFile()预热后还会调用mlock方法, 将预热后的内存空间进行锁定,来防止操作系统将内存空间调到swap空间中。