NIO-内存映射文件-理论篇

简介: 内存映射文件是一种允许程序直接从内存访问的特殊文件。通过将整个文件或者文件的一部分映射到内存中、操作系统负责获取页面请求和写入文件,应用程序就只需要处理内存数据,这样可以实现非常快速的 IO 操作。
我是石页兄,朋友不因远而疏,高山不隔友谊情;偶遇美羊羊,我们互相鼓励

欢迎关注微信公众号「架构染色」交流和学习

一、内存映射文件的由来

1.1 基于缓冲区的 I/O 操作

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

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

  1. 进程使用 read()系 统调用,要求其缓冲区被填满。
  2. 内核随即向磁盘控制硬件发出命令,要求其从磁盘读取数据。

    • 磁盘 控制器把数据直接写入内核内存缓冲区,这一步通过 DMA 完成,无需主 CPU 协助。
    • 一旦磁盘控 制器把缓冲区装满,内核即把数据从内核空间的临时缓冲区拷贝到进程执行 read( )调用时指定的缓 冲区。

image.png

1.2 缓冲区在哪里?

image.png

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

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

  1. 磁盘中的数据先拷贝到内核空间的物理内存中。
  2. 内核空间的物理内存中,再拷贝到用户空间的物理内存中。

image.png

1.3 内存的拷贝影响性能

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

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

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

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

image.png

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

image.png

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

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

二、内存映射文件的使用

2.1 使用者的视角

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

2.2 示例代码

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 可能是清除内存映射文件的唯一选择

四、最后说一句

我是石页兄,如果这篇文章对您有帮助,或者有所启发的话,欢迎关注笔者的微信公众号【 架构染色 】进行交流和学习。您的支持是我坚持写作最大的动力。

欢迎点击链接扫马儿关注、交流。

参考并感谢

相关文章
|
3月前
|
存储 缓存 Linux
用户态内存映射
【9月更文挑战第20天】内存映射不仅包括物理与虚拟内存间的映射,还涉及将文件内容映射至虚拟内存,使得访问内存即可获取文件数据。mmap 系统调用支持将文件或匿名内存映射到进程的虚拟内存空间,通过多级页表机制实现高效地址转换,并利用 TLB 加速映射过程。TLB 作为页表缓存,存储频繁访问的页表项,显著提升了地址转换速度。
|
2月前
|
Linux C++
Linux c/c++文件虚拟内存映射
这篇文章介绍了在Linux环境下,如何使用虚拟内存映射技术来提高文件读写的速度,并通过C/C++代码示例展示了文件映射的整个流程。
50 0
|
2月前
|
程序员 Windows
程序员必备文件搜索工具 Everything 带安装包!!! 比windows自带的文件搜索快几百倍!!! 超级好用的文件搜索工具,仅几兆,不占内存,打开即用
文章推荐了程序员必备的文件搜索工具Everything,并提供了安装包下载链接,强调其比Windows自带搜索快且占用内存少。
48 0
|
3月前
|
存储 安全 Linux
将文件映射到内存,像数组一样访问
将文件映射到内存,像数组一样访问
33 0
|
3月前
|
消息中间件 Linux 容器
共享内存的创建和映射过程
【9月更文挑战第1天】消息队列、共享内存及信号量在使用前需生成key并获取唯一ID,均通过`xxxget`函数实现。
|
5月前
|
Java
jmap 查看jvm内存大小并进行dump文件内存分析
jmap 查看jvm内存大小并进行dump文件内存分析
102 3
|
6月前
|
Python
python3获取内存和cpu利用率记录日志文件psutil
python3获取内存和cpu利用率记录日志文件psutil
73 1
|
6月前
|
Java 程序员 Linux
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
50 0
|
6月前
|
监控 Linux
深入了解Linux的pmap命令:进程内存映射的利器
`pmap`是Linux下分析进程内存映射的工具,显示内存区域、权限、大小等信息。通过`/proc/[pid]/maps`获取数据,特点包括详细、实时和灵活。参数如`-x`显示扩展信息,`-d`显示设备。示例:`pmap -x 1234`查看进程1234的映射。注意权限、实时性和准确性。结合其他工具定期监控,排查内存问题。
|
6月前
|
存储 监控 Java
深入探索Java BIO与NIO输入输出模型:基于文件复制和socket通信
深入探索Java BIO与NIO输入输出模型:基于文件复制和socket通信