Linux IO模型
Linux系统中的磁盘文件访问方式包括:
- 缓存IO(Buffer IO),又称标准IO,是多数OS的默认IO模式,在缓存IO模式,读文件操作时,数据先从磁盘复制到内核空间缓冲区,然后再从内核缓冲区复制到应用程序地址空间,写操作亦然。
- 直接IO(Direct IO),此模式下,应用程序读写文件时直接访问磁盘数据,不经过内核缓冲区,适用于数据库等程序自己管理缓冲的场景。
- 内存映射(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直接内存映射
JDK1.4的NIO中引入了直接内存映射的模式,能直接在Native堆中分配内存,以避免JVM堆和Native堆之间数据拷贝带来的性能损耗。JVM堆内存和Native堆内存分别对应Linux缓存IO模式中的应用程序内存和Kernel内存(这个待考证,但至少可以类比)。
Java进程内存基本结构如下:
通过使用堆外内存,可以带来以下好处:
- 改善堆过大时垃圾回收效率,减少停顿。Full GC时会扫描堆内存,回收效率和堆大小成正比。通过把内存放到Native堆,可提升GC效率。Native的内存,由OS负责管理和回收。
- 减少内存在Native堆和JVM堆拷贝过程,避免拷贝损耗,降低内存使用。
- 可突破JVM内存大小限制。
创建直接内存
Java中ByteBuffer用于创建内存缓存,其类的继承关系如下:
其中HeapByteBuffer用于创建JVM堆内缓存区,DirectByteBuffer用于创建Native缓存区。通过调用ByteBuffer
的静态方法allocate
和allocateDirect
方法分别创建两种缓存区。
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) {
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。