我正在做一些用Java编写的应用程序的基准测试.对于实验来说非常重要的是结果不受页面缓存的影响(我正在使用linux)
因此,无论何时打开文件,避免页面缓存的最佳方法是使用O_DIRECT.因此,我改变了jre源代码中的相应代码.
我的方法适用于通过FileOutputStream
(例如写作)的所有内容,但它不适用于FileInputStream
(例如阅读).
将O_DIRECT添加到open-call时FileInputStream
,JVM无法加载任何类:
Error: Could not find or load main class perf.TestDirectIO
Run Code Online (Sandbox Code Playgroud)
这个错误是不是一个类路径的问题,因为我可以通过使用"未受攻击" JVM修复它.
因此打开文件似乎存在问题.
我对如何解决这个问题的任何建议感到非常高兴.
如果有人想做类似的事情,我已经在我的博客中记录了整个黑客.
作为参考,这些是我对JVM代码所做的更改:
jdk/src/share/native/java/io/FileInputStream.c
:
@@ -58,7 +60,8 @@
JNIEXPORT void JNICALL
Java_java_io_FileInputStream_open(JNIEnv *env, jobject this, jstring path) {
- fileOpen(env, this, path, fis_fd, O_RDONLY);
+ fileOpen(env, this, path, fis_fd, O_RDONLY | O_DIRECT); // this is the change that causes all the problems
}
Run Code Online (Sandbox Code Playgroud)
此更改有效
jdk/src/solaris/native/java/io/FileOutputStream_md.c
:
@@ -55,8 +55,10 @@
JNIEXPORT void JNICALL
Java_java_io_FileOutputStream_open(JNIEnv *env, jobject this,
jstring path, jboolean append) {
fileOpen(env, this, path, fos_fd,
- O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC));
+ O_WRONLY | O_DIRECT | O_CREAT | (append ? O_APPEND : O_TRUNC));
}
Run Code Online (Sandbox Code Playgroud)
我还更改了热点jre以确保内存对齐(这是O_DIRECT的要求)
hotspot/src/share/vm/runtime/os.cpp
:
+# include <mm_malloc.h>
...
- u_char* ptr = (u_char*)::malloc(size + space_before + space_after);
+ u_char* ptr = (u_char*)::_mm_malloc(size + space_before + space_after,512);
Run Code Online (Sandbox Code Playgroud)
Run Code Online (Sandbox Code Playgroud)"The thing that has always disturbed me about O_DIRECT is that the whole interface is just stupid, and was probably designed by a deranged monkey on some serious mind-controlling substances [*]."
[*]换句话说,它是一个Oracleism.
- 来自Transmeta的Linus Torvalds,2002年5月11日
检查以下部分man 2 open
:
O_DIRECT
O_DIRECT标志可能会对 用户空间缓冲区的长度和地址以及I/O 的文件偏移量施加对齐限制.在Linux中,对齐限制因文件系统和内核版本而异....
在Linux 2.4下,传输大小,用户缓冲区和文件偏移的对齐必须都是文件系统逻辑块大小的倍数.在Linux 2.6下,对齐到512字节边界就足够了.....
总之,O_DIRECT是一个潜在的强大工具,应谨慎使用.建议应用程序将O_DIRECT用作性能选项,默认情况下禁用该选项.
我认为,在JRE(类加载器)中有一些FileInputStream用法,其读取的偏移量或大小未对齐到512字节.(对于高级格式,最小对齐可能更大,甚至4096字节,或一个4K页面.)
内核对于未对齐偏移的行为是灰色区域,一些信息在这里:RFC:澄清直接I/O语义,Theodore Ts'o,tytso @ mit,LWN,2009
其他有趣的讨论在这里:Linux:使用O_DIRECT访问文件(kerneltrap,2007)
嗯,当DIRECT出现故障时,似乎应该回退到缓冲的I/O. 使用DIRECT的所有IO操作都是同步的.可能是一些DMA效应?或者O_DIRECT
和mmap
?的组合?
更新:
感谢strace输出.这是错误(grep O_DIRECT
然后检查文件描述符操作):
28290 open("...pact/perf/TestDirectIO.class", O_RDONLY|O_DIRECT) = 11
28290 fstat(11, {st_mode=S_IFREG|0644, st_size=2340, ...}) = 0
28290 fcntl(11, F_GETFD) = 0
28290 fcntl(11, F_SETFD, FD_CLOEXEC) = 0
...skip
28290 stat("...pact/perf/TestDirectIO.class", {st_mode=S_IFREG|0644, st_size=2340, ...}) = 0
...skip
28290 read(11, "\312\376\272\276\0\0\0003\0\215\n\0-\0D\t\0E\0F\7\0G\n\0\3\0D\10\0H\n"..., 1024) = 1024
28290 read(11, 0x7f1d76d23a00, 1316) = -1 EINVAL (Invalid argument)
Run Code Online (Sandbox Code Playgroud)
未对齐的读取大小会导致EINVAL
错误.您的类文件长度为2340字节,它是1024 + 1316字节,未对齐.
您可以在 Java 下使用 O_DIRECT,利用Java Native Access (JNA)。此处提供了启用 O_DIRECT 的 InputStream 和 OutputStream 的实现。
只是为了更新,因为这篇文章是第一个出现的文章,如果你谷歌O_DIRECT JVM
:
O_DIRECT
2017 年,执行直接 I/O 的功能已合并到 JDK 中:
创建 FileChannel 时,可以传递ExtendedOpenOption.DIRECT
:
import com.sun.nio.file.ExtendedOpenOption;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
FileChannel fc = FileChannel.open(f.toPath(), StandardOpenOption.WRITE,
ExtendedOpenOption.DIRECT);
Run Code Online (Sandbox Code Playgroud)
2022 年,还可以考虑将新的外部内存 API 与MemorySegment
后备缓冲区一起使用。