在Java中真正强制文件同步/刷新

7 浏览
0 Comments

在Java中真正强制文件同步/刷新

如何通过Java将写入文件的数据真正地刷新/同步到块设备上。

我用NIO尝试了以下代码:

FileOutputStream s = new FileOutputStream(filename)
Channel c = s.getChannel()
while(xyz)
    c.write(buffer)
c.force(true)
s.getFD().sync()
c.close()

我认为c.force(true)和s.getFD().sync()应该足够了,因为force的文档中写到:

强制将对此通道文件的任何更新写入包含该文件的存储设备。

如果此通道的文件驻留在本地存储设备上,则此方法返回时,保证自从创建此通道或自从上次调用此方法以来对文件所做的所有更改都已写入该设备。这对于确保在系统崩溃时不会丢失关键信息非常有用。

sync的文档中写到:

强制所有系统缓冲区与底层设备同步。此方法在所有修改过的数据和此FileDescriptor的属性已写入相关设备之后返回。特别地,如果此FileDescriptor引用物理存储介质(如文件系统中的文件),则sync将在所有与此FileDesecriptor相关的内存中修改的缓冲区副本已写入物理介质之后返回。sync的目的是供需要物理存储(如文件)处于已知状态的代码使用。

这两个调用应该足够了。对吗?我猜想它们并不足够。

背景:我进行了一个小的性能比较(2 GB,顺序写入),使用C/Java,Java版本的速度是C版本的两倍,可能比硬件更快(单个硬盘上的120 MB/s)。我还尝试使用Runtime.getRuntime().exec("sync")来执行命令行工具sync,但这并没有改变行为。

C代码的运行结果是70 MB/s(使用底层API(open,write,close)不会有太大改变):

FILE* fp = fopen(filename, "w");
while(xyz) {
    fwrite(buffer, 1, BLOCK_SIZE, fp);
}
fflush(fp);
fclose(fp);
sync();

如果没有最后一次对sync的调用,我得到的值是不现实的(超过1 GB,即主内存性能)。

为什么C和Java之间有这么大的差异?有两种可能性:我在Java中没有正确地同步数据,或者C代码由于某种原因不够优化。

更新:

我使用"strace -cfT cmd"运行了strace。以下是结果:

C(底层API):

MB/s 67.389782

% time seconds usecs/call calls errors syscall

------ ----------- ----------- --------- --------- ----------------

87.21 0.200012 200012 1 fdatasync

11.05 0.025345 1 32772 write

1.74 0.004000 4000 1 sync

C(高级API):

MB/s 61.796458

% time seconds usecs/call calls errors syscall

------ ----------- ----------- --------- --------- ----------------

73.19 0.144009 144009 1 sync

26.81 0.052739 1 65539 write

Java(1.6 SUN JRE,java.io API):

MB/s 128.6755466197537

% time seconds usecs/call calls errors syscall

------ ----------- ----------- --------- --------- ----------------

80.07 105.387609 3215 32776 write

2.58 3.390060 3201 1059 read

0.62 0.815251 815251 1 fsync

Java(1.6 SUN JRE,java.nio API):

MB/s 127.45830221558376

5.52 0.980061 490031 2 fsync

1.60 0.284752 9 32774 write

0.00 0.000000 0 80 close

时间值似乎只是系统时间,因此意义不大。

更新2:

我切换到另一个服务器,重新启动,并使用新格式化的ext3。现在Java和C之间只有4%的差异。我不知道出了什么问题。有时候事情很奇怪。在写这个问题之前,我应该尝试在另一个系统上进行测量。对不起。

更新3:

总结答案如下:

  • 对于Java NIO,请使用c.force(true),然后是s.getFD().sync();对于Java的流API,请使用s.flush(),然后是s.getFD().sync()。对于C的高级API,请不要忘记sync。fflush将数据提交给操作系统,但不会将数据传输到块设备。
  • 使用strace来分析一个命令执行的系统调用
  • 在发布问题之前先交叉验证您的结果。

更新4:

请注意以下的后续问题

0