- 操作系统IO分为 直接IO和缓存IO
可以参考:
1. https://www.icode9.com/content-4-450323.html
2. https://blog.youkuaiyun.com/lzh1179805109/article/details/79883523 - 简单的理解缓存IO就是:可以把缓存IO认为是操作系统给磁盘增加的缓存,这样的话,用户进程的数据write和read其实针对的就是内核的page cache(磁盘缓存),至于page cache什么时候和磁盘同步,这个在此不多介绍,可以搜索一下,默认是脏页的同步。
- Linux系统默认就是使用的缓存IO,所以咱们代码里的数据读写,也就会发生至少两次拷贝:一、从用户进程地址空间拷贝到内核空间的page cache;二、从内核page cache拷贝到磁盘(此处还涉及到寻址);
- 那么对于Java程序来说:因为使用了JVM,其实就更复杂了。因为JVM相对于操作系统就是用户进程,但是JVM里面又划分了一个JVM的堆,所以其实所谓的堆外空间的IO才是上面讲的 用户进程地址空间拷贝到内核缓存。那么jvm堆内的数据的IO呢?参照这篇文章https://blog.youkuaiyun.com/qq_21125183/article/details/88701156下面作者给读者的评论,是说
- 正常IO是两次拷贝,但是由于Java 中堆的GC机制,在IO操作时会导致地址变化移动(但是C调用IO时要求地址是不能发生变化,否则出错),所以会拷贝到堆外内存,从而存在堆内与堆外多一次的复制。
- 也就是因为jvm因为通过调用native方法,从而使用的C语言封装的系统调用,从而必须要满足C语言调用IO的限制(要求IO时地址不能发生变化),所以jvm因为堆内内存可能发生gc,导致数据使用的地址变化,从而在进行IO的时候,需要有堆内堆外拷贝的过程,拷贝到堆外内存之后再通过C语言的系统调用进行用户进程和操作系统内核缓存数据的交互。
- 也因此解释了,C语言没有垃圾回收,也没有内存整理,需要用户手动free或者delete内存的占用;而Java用了gc,方便了用户,牺牲了IO性能。因为按照上面的理解,Java程序在发生IO时,比C语言的进程更多一次数据拷贝。
FileChannelImpl#write
public int write(ByteBuffer var1) throws IOException {
this.ensureOpen();
if (!this.writable) {
throw new NonWritableChannelException();
} else {
synchronized(this.positionLock) {
int var3 = 0;
int var4 = -1;
byte var5;
try {
this.begin();
var4 = this.threads.add();
if (this.isOpen()) {
do {
// 可以看到此处的调用, fd是文件描述符,操作系统层面对应某个文件
var3 = IOUtil.write(this.fd, var1, -1L, this.nd);
} while(var3 == -3 && this.isOpen());
int var12 = IOStatus.normalize(var3);
return var12;
}
var5 = 0;
} finally {
this.threads.remove(var4);
this.end(var3 > 0);
assert IOStatus.check(var3);
}
return var5;
}
}
}
IOUtil.write
static int write(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
// 如果是直接内存(堆外内存)缓冲,直接把数据写入操作系统的page cache
if (var1 instanceof DirectBuffer) {
return writeFromNativeBuffer(var0, var1, var2, var4);
} else {
int var5 = var1.position();
int var6 = var1.limit();
assert var5 <= var6;
int var7 = var5 <= var6 ? var6 - var5 : 0;
// 从直接内存(堆外内存)中根据需要写入数据大小分配一块临时大小的空间
ByteBuffer var8 = Util.getTemporaryDirectBuffer(var7);
int var10;
try {
// 把堆内缓冲数据拷贝到堆外内存中
var8.put(var1);
var8.flip();
var1.position(var5);
// 把堆外内存数据写入操作系统的 page cache
int var9 = writeFromNativeBuffer(var0, var8, var2, var4);
if (var9 > 0) {
var1.position(var5 + var9);
}
var10 = var9;
} finally {
Util.offerFirstTemporaryDirectBuffer(var8);
}
return var10;
}
}