Java高效NIO之直接内存映射

Linux IO模型

Linux系统中的磁盘文件访问方式包括:

  1. 缓存IO(Buffer IO),又称标准IO,是多数OS的默认IO模式,在缓存IO模式,读文件操作时,数据先从磁盘复制到内核空间缓冲区,然后再从内核缓冲区复制到应用程序地址空间,写操作亦然。
  2. 直接IO(Direct IO),此模式下,应用程序读写文件时直接访问磁盘数据,不经过内核缓冲区,适用于数据库等程序自己管理缓冲的场景。
  3. 内存映射(Memory Mapping),这是Linux提供的一种访问磁盘的特殊方式,它把内存中的某块地址空间和磁盘文件直接关联,从而把对内存的访问直接转换为对磁盘的访问。

各种IO模式的其中一个区别是对Kernel内存的使用方式。
缓存IO模式下,用户程序通过read读取数据时,先检查Kernel缓存区是否有需要的数据,有则拷贝数据到用户程序缓存区;没有则从磁盘读取,先缓存到Kernel中,再拷贝到用户程序缓存;write操作时,把写入数据从用户地址空间拷贝到Kernel缓存区(Kernel缓存区写入后,用户程序写操作已完成,数据刷到磁盘的时间由OS来决定,或由程序显示调用sync命令)。
直接IO模式下,用户进程访问文件时,跨过Kernel缓冲区直接访问磁盘,有效避免了CPU和内存的多余开销。例如,对于数据库服务等比较复杂的应用,程序根据业务更懂如何使用内存,为了提高性能,希望绕过内核缓存区,由自己在用户空间管理IO缓存,包括缓存机制和写延迟机制等,以支持事务、提高查询缓存命中率等。
内存映射模式下,通过mmap系统调用,在用户进程虚拟内存地址和Kernel内存间直接建立映射关系。通过用户缓存和Kernel缓存的共享,用户程序的操作直接作用到Kernel内存,无需进行内存拷贝。使用内存映射文件处理磁盘上的文件时,无需对文件执行IO操作,也不需要再为文件进行内存分配、加载和释放等管理工作,因此此模式在处理大量数据的文件时能起到高效的作用。

Java高效NIO之直接内存映射_第1张图片

Java NIO直接内存映射

JDK1.4的NIO中引入了直接内存映射的模式,能直接在Native堆中分配内存,以避免JVM堆和Native堆之间数据拷贝带来的性能损耗。JVM堆内存Native堆内存分别对应Linux缓存IO模式中的应用程序内存和Kernel内存(这个待考证,但至少可以类比)。

Java进程内存基本结构如下:
Java高效NIO之直接内存映射_第2张图片

通过使用堆外内存,可以带来以下好处:

  1. 改善堆过大时垃圾回收效率,减少停顿。Full GC时会扫描堆内存,回收效率和堆大小成正比。通过把内存放到Native堆,可提升GC效率。Native的内存,由OS负责管理和回收。
  2. 减少内存在Native堆和JVM堆拷贝过程,避免拷贝损耗,降低内存使用。
  3. 可突破JVM内存大小限制。

创建直接内存

Java中ByteBuffer用于创建内存缓存,其类的继承关系如下:
Java高效NIO之直接内存映射_第3张图片

其中HeapByteBuffer用于创建JVM堆内缓存区,DirectByteBuffer用于创建Native缓存区。通过调用ByteBuffer的静态方法allocateallocateDirect方法分别创建两种缓存区。

public static ByteBuffer allocate(int capacity) {  
    if (capacity < 0)  
        throw new IllegalArgumentException();  
 return new HeapByteBuffer(capacity, capacity);  
}

public static ByteBuffer allocateDirect(int capacity) {  
    return new DirectByteBuffer(capacity);  
}

MappedByteBuffer是NIO提供的文件内存映射的实现方案,可以把整个文件或文件段映射到Native堆内存。MappedByteBuffer是抽象内,DirectByteBuffer才是提供实际功能的其直接子类,他同时实现了DirectBuffer接口,此接口提供了cleaner用于GC管理。

通过FileChannel的map方法,可以把文件映射为内存对象:

public abstract MappedByteBuffer map(MapMode mode,  
 long position, long size)  
    throws IOException;

MapMode提供了3种内存映射模式:

  • READ_ONLY:read-only mapping
  • READ_WRITE:read/write mapping
  • PRIVATE:private (copy-on-write) mapping,此模式下,对内存映射的修改,不会写入文件,而是创建修改后缓冲区的私有副本。
使用参数-XX:MaxDirectMemorySize指定DirectByteBuffer的大小。

使用内存映射读取文件的代码参考如下:

public static void readFileWithMmap(File file) {
    int BUFFER_SIZE = 1024;
    byte[] b = new byte[BUFFER_SIZE];
    int len = (int) file.length();
    MappedByteBuffer buff;
    try (FileChannel channel = new FileInputStream(file).getChannel()) {
        buff = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
        for (int offset = 0; offset < len; offset += BUFFER_SIZE) {
            if (len - offset > BUFFER_SIZE) {
                buff.get(b);
            } else {
                buff.get(new byte[len - offset]);
            }
        }
    } catch (IOException e) {
    }
}

你可能感兴趣的:(nio,mmap)