Java如何保证文件落盘?
在之前的文章Linux/UNIX编程如何保证文件落盘中,我们聊了从应用到操作系统,我们要如何保证文件落盘,来确保掉电等故障不会导致数据丢失。JDK也封装了对应的功能,并且为我们做好了跨平台的保证。
JDK中有三种方式可以强制文件数据落盘:
- 调用
FileDescriptor#sync
函数 - 调用
FileChannel#force
函数 - 使用
RandomAccessFile
以rws
或者rwd
模式打开文件
FileDescriptor#sync
FileDescriptor
类提供了sync
方法,可以用于保证数据保存到持久化存储设备后返回:
1 | FileOutputStream outputStream = new FileOutputStream("/Users/mazhibin/b.txt"); |
可以看一下JDK是如何实现FileDescriptor#sync
的:
1 | public native void sync() throws SyncFailedException; |
1 | // jdk/src/solaris/native/java/io/FileDescriptor_md.c |
IO_Sync
在UNIX系统上的定义就是fsync
:
1 | // jdk/src/solaris/native/java/io/io_util_md.h |
FileChannel#force
之前的文章提到了,操作系统提供了fsync
/fdatasync
两个用户同步数据到持久化设备的系统调用,后者尽可能的会不同步文件元数据,来减少一次磁盘IO,提高性能。但是Java IO的FileDescriptor#sync
只是对fsync的封装,JDK中没有对于fdatasync
的封装,这是一个特性缺失。
Java NIO对这一点也做了增强,FileChannel
类的force
方法,支持传入一个布尔参数metaData
,表示是否需要确保文件元数据落盘,如果为true
,则调用fsync
。如果为false
,则调用fdatasync
。
使用范例:
1 | FileOutputStream outputStream = new FileOutputStream("/Users/mazhibin/b.txt"); |
我们来看看其实现:
1 | public class FileChannelImpl extends FileChannel { |
实现中有许多线程同步相关的代码,不属于我们要关注的部分,就不分析了。FileChannel#force
调用FileDispatcher#force
。
FileDispatcher
是NIO内部实现用的一个类,封装了一些文件操作方法,其中包含了刷新文件的方法:
1 | abstract class FileDispatcher extends NativeDispatcher { |
FileDispatcher#force
的实现:
1 | class FileDispatcherImpl extends FileDispatcher |
FileDispatcher#force
的本地方法实现:
1 | JNIEXPORT jint JNICALL |
可以看出,其实就是简单的通过metaData
参数来区分调用fsync
和fdatasync
。
RandomAccessFile结合rws/rwd模式
RandomAccessFile
打开文件支持4中模式:
- “r” 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
- “rw” 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
- “rws” 打开以便读取和写入,对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
- “rwd” 打开以便读取和写入,对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备。
其中rws
模式会在open
文件时传入O_SYNC
标志位。rwd
模式会在open
文件时传入O_DSYNC
标志位。
具体的源码分析参考:JDK源码阅读-RandomAccessFile