美文网首页
NIO-内存映射文件

NIO-内存映射文件

作者: rock_fish | 来源:发表于2020-05-20 00:07 被阅读0次

    内存映射文件的由来

    1. 基于缓冲区的I/O操作

    I/O 的基础是缓冲区,所谓“输入/输出”简单来说就是把数据移进或移出缓冲区.

    数据从外部磁盘向运行中的进程的内存区域移动的过程大致如下:

    1. 进程使用read( )系 统调用,要求其缓冲区被填满。
    2. 内核随即向磁盘控制硬件发出命令,要求其从磁盘读取数据。
      2.1. 磁盘 控制器把数据直接写入内核内存缓冲区,这一步通过 DMA 完成,无需主 CPU 协助。
      2.2. 一旦磁盘控 制器把缓冲区装满,内核即把数据从内核空间的临时缓冲区拷贝到进程执行read( )调用时指定的缓 冲区。
    image.png
    2. 缓冲区在哪里?
    image.png

    通过上图,可清晰看到内核空间用户空间的缓冲区其实都是在物理内存中,此处会有疑问为什么要分要分两个空间,要2个缓冲区?

    这是因为操作系统的设计,要求内核空间和用户空间必须隔离,以保护内核空间.而磁盘和内存之间的数据复制操作的也只能发生在内核空间中.所以会发生2次数据拷贝:

    1. 磁盘中的数据先拷贝到内核空间的物理内存中.
    2. 内核空间的物理内存中,再拷贝到用户空间的物理内存中.
    image.png
    3. 内存的拷贝影响性能

    许多经验告诉我们,内存的申请\释放 和 内存数据的copy 的性能消耗都很大,是可优化的点,比如使用池化技术减少内存的申请\释放,尽量避免无必要的内存数据copy.

    那内核缓冲->用户缓冲的内存拷贝能否省掉?答案是肯定的.

    4. 内核空间\用户空间共享内存缓冲区

    从下图可见,如果内核空间\用户空间共享内存缓冲区,那么当内核将磁盘数据读取到内存后,用户空间就直接可见了.

    image.png

    那么就可以简单的理解为如下图这般,文件直接映射到了用户空间可见的内存中.


    image.png

    从实现的角度来看这个过程是下图所示这般:

    1. 进程中通过mmap 映射一个文件,得到一个buffer块(这个buffer的起止地址都是逻辑地址),在执行跟地址相关的指令时,cpu通过MMU将其逻辑地址转换为真实的物理内存地址.也即是,进程通过逻辑地址来操控物理内存.此时只是在操作系统内部建立了映射的关键,还没有真正的将磁盘的数据读取到物理内存中.
    2. 当进程要读取这个buffer块的时候,会判断其对应的物理内存中是否有在物理内存中,若不存在就从磁盘文件中读取,放置到物理内存中,而这个从磁盘到物理内存的过程实际上是虚拟内存的页交换技术. 关于这个也交换的相关知识不在这里展开.
    image.png

    内存映射文件的使用

    使用者的视角

    内存映射文件是一种允许程序直接从内存访问的特殊文件。通过将整个文件或者文件的一部分映射到内存中、操作系统负责获取页面请求和写入文件,应用程序就只需要处理内存数据,这样可以实现非常快速的IO操作。用于内存映射文件的内存在Java的堆空间以外。Java中的java.nio包支持内存映射文件,可以使用MappedByteBuffer来读写内存。

    实例代码

    import java.io.RandomAccessFile;
    import java.nio.MappedByteBuffer;
    import java.nio.channels.FileChannel;
    public class MemoryMappedFileInJava {
    private static int count = 10485760; // 10 MB
    public static void main(String[] args) throws Exception {
        RandomAccessFile memoryMappedFile = new RandomAccessFile("largeFile.txt", "rw");
        // Mapping a file into memory
        MappedByteBuffer out = memoryMappedFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, count);
        // Writing into Memory Mapped File
        for (int i = 0; i < count; i++) {
            out.put((byte) 'A');
        }
        System.out.println("Writing to Memory Mapped File is completed");
        // reading from memory file in Java
        for (int i = 0; i < 10; i++) {
            System.out.print((char) out.get(i));
        }
        System.out.println("Reading from Memory Mapped File is completed");
        memoryMappedFile.close();
    }
    

    注意事项

    1. Java语言通过java.nio包支持内存映射文件和IO。
    2. 内存映射文件用于对性能要求高的系统中,如繁忙的电子交易系统
    3. 使用内存映射IO你可以将文件的一部分加载到内存中
    4. 如果被请求的页面不在内存中,内存映射文件会导致页面错误
    5. 将一个文件区间映射到内存中的能力取决于内存的可寻址范围。在32位机器中,不能超过4GB,即2^32比特。
    6. Java中的内存映射文件比流IO要快(译注:对于大文件而言是对的,小文件则未必)
    7. 用于加载文件的内存在Java的堆内存之外,存在于共享内存中,允许两个不同进程访问文件。顺便说一下,这依赖于你用的是direct还是non-direct字节缓存。
    8. 读写内存映射文件是操作系统来负责的,因此,即使你的Java程序在写入内存后就挂掉了,只要操作系统工作正常,数据就会写入磁盘。
    9. Direct字节缓存比non-direct字节缓存性能要好
    10. 不要经常调用MappedByteBuffer.force()方法,这个方法强制操作系统将内存中的内容写入硬盘,所以如果你在每次写内存映射文件后都调用force()方法,你就不能真正从内存映射文件中获益,而是跟disk IO差不多。
    11. 如果电源故障或者主机瘫痪,有可能内存映射文件还没有写入磁盘,意味着可能会丢失一些关键数据。
    12. MappedByteBuffer和文件映射在缓存被GC之前都是有效的。sun.misc.Cleaner可能是清除内存映射文件的唯一选择。

    感谢你们:

    RandomAccessFile 与 内存映射文件
    再谈MappedByteBuffer与DirectBuffer
    零拷贝
    Linux | 为什么用户态和内核态的切换耗费时间?
    浅谈 Linux下的零拷贝机制

    相关文章

      网友评论

          本文标题:NIO-内存映射文件

          本文链接:https://www.haomeiwen.com/subject/mqmqohtx.html