FileOutputStream写入后数据未落盘,因数据先存于内核页缓存,需调用getFD().sync()触发fsync(2)强制持久化;flush()仅清Java缓冲,不保证磁盘写入。

FileOutputStream 写入后数据为什么还没进磁盘?
默认情况下,FileOutputStream 写入的数据先经过内核页缓存(page cache),操作系统会延迟刷盘以提升性能。这意味着即使 write() 返回、甚至 close() 完成,数据仍可能只在内存里——断电或崩溃会导致丢失。
真正落盘需触发内核将对应文件的脏页写入块设备,FileDescriptor.sync() 就是干这个的:它调用 fsync(2) 系统调用,等待所有已写入的修改(包括元数据)持久化到磁盘。
- 仅对已打开的
FileOutputStream生效,且必须在写入后、流关闭前调用 - 不保证其他进程或线程的写入,只同步当前
FileDescriptor关联的文件 - 调用失败会抛出
IOException(如磁盘满、I/O 错误),必须显式捕获处理
如何安全调用 FileDescriptor.sync()?
不能直接 new FileOutputStream(...).getFD().sync() —— 因为 getFD() 返回的 FileDescriptor 在流关闭后即失效,而 sync() 必须在流还活着时执行。
正确顺序是:写入 → flush()(确保 Java 缓冲区清空到内核)→ getFD().sync() → close()。
-
flush()不是可选的:如果FileOutputStream包装了缓冲(如BufferedOutputStream),不flush()会导致部分数据卡在 Java 层,sync()无法覆盖这部分 - 务必在
try-with-resources或finally块中调用sync(),避免因异常跳过 - 不要在循环中频繁调用
sync():它会阻塞当前线程直到 I/O 完成,严重拖慢吞吐
try (FileOutputStream fos = new FileOutputStream("data.bin")) {
fos.write(new byte[]{1, 2, 3});
fos.flush(); // 确保字节到达内核
fos.getFD().sync(); // 强制落盘
} catch (IOException e) {
// 处理 sync 失败(如 IOException: Input/output error)
}
sync() 和 getChannel().force(true) 有什么区别?
两者都最终调用 fsync(2),但入口不同:FileDescriptor.sync() 是面向传统流的底层接口,FileChannel.force(true) 是 NIO 的等价封装。
- 行为一致:都同步数据和元数据(如修改时间、文件大小)
- 若你用的是
FileOutputStream,优先用getFD().sync(),避免额外创建FileChannel -
force(false)只同步数据(跳过元数据),但FileDescriptor.sync()没有该选项,它总是全量同步 - 注意:
FileChannel的force()必须在channel.isOpen()为 true 时调用,且不能在流关闭后复用
sync() 调用失败意味着什么?
常见错误信息如 java.io.IOException: Input/output error 或 java.io.IOException: No space left on device,说明内核已拒绝落盘请求。
- 磁盘硬件故障、文件系统只读、空间耗尽、挂载参数含
noatime或barrier=0都可能导致sync()失败 - 不要忽略异常:返回值为 void,失败只能靠异常感知;静默吞掉异常等于放弃数据一致性保障
- 生产环境建议记录错误日志,并考虑降级策略(如转存到备用路径、告警人工介入)
真正需要 sync() 的场景其实很窄:金融交易日志、数据库 WAL、配置热更新等强持久性要求场合。多数普通文件写入没必要加,它带来的延迟和 I/O 压力远超收益。











