Java 10(Centos)上的RandomAccessFile.setLength慢得多 - java

以下代码

public class Main {
    public static void main(String[] args) throws IOException {
        File tmp = File.createTempFile("deleteme", "dat");
        tmp.deleteOnExit();
        RandomAccessFile raf = new RandomAccessFile(tmp, "rw");
        for (int t = 0; t < 10; t++) {
            long start = System.nanoTime();
            int count = 5000;
            for (int i = 1; i < count; i++)
                raf.setLength((i + t * count) * 4096);
            long time = System.nanoTime() - start;
            System.out.println("Average call time " + time / count / 1000 + " us.");
        }
    }
}

在Java 8上,此程序运行良好(该文件位于tmpfs上,因此您希望它是微不足道的)

Average call time 1 us.
Average call time 0 us.
Average call time 0 us.
Average call time 0 us.
Average call time 0 us.
Average call time 0 us.
Average call time 0 us.
Average call time 0 us.
Average call time 0 us.
Average call time 0 us.

在Java 10上,随着文件变大,这个速度变慢

Average call time 311 us.
Average call time 856 us.
Average call time 1423 us.
Average call time 1975 us.
Average call time 2530 us.
Average call time 3045 us.
Average call time 3599 us.
Average call time 4034 us.
Average call time 4523 us.
Average call time 5129 us.

有没有办法诊断这种问题?

是否有在Java 10上有效运行的解决方案或替代方案?

注意:我们可以写到文件的末尾,但是这需要锁定它,我们希望避免这样做。

为了进行比较,在Windows 10,Java 8(不是tmpfs)上

Average call time 542 us.
Average call time 487 us.
Average call time 480 us.
Average call time 490 us.
Average call time 507 us.
Average call time 559 us.
Average call time 498 us.
Average call time 526 us.
Average call time 489 us.
Average call time 504 us.

Windows 10,Java 10.0.1

Average call time 586 us.
Average call time 508 us.
Average call time 615 us.
Average call time 599 us.
Average call time 580 us.
Average call time 577 us.
Average call time 557 us.
Average call time 572 us.
Average call time 578 us.
Average call time 554 us.

UPDATE似乎Java 8和10之间的系统调用选择已更改。这可以通过将strace -f放在命令行开头来看到。

在Java 8中,以下调用在内部循环中重复

[pid 49027] ftruncate(23, 53248)        = 0
[pid 49027] lseek(23, 0, SEEK_SET)      = 0
[pid 49027] lseek(23, 0, SEEK_CUR)      = 0

在Java 10中,重复以下调用

[pid   444] fstat(8, {st_mode=S_IFREG|0664, st_size=126976, ...}) = 0
[pid   444] fallocate(8, 0, 0, 131072)  = 0
[pid   444] lseek(8, 0, SEEK_SET)       = 0
[pid   444] lseek(8, 0, SEEK_CUR)       = 0

特别是,fallocateftruncate做更多的工作,并且花费的时间似乎与文件的长度成正比,而不是与添加到文件的长度成正比。

一种解决方法是:

  • 使用反射到fd文件描述符
  • 使用JNA或FFI调用ftruncate。
  • 这似乎是一个棘手的解决方案。 Java 10中还有更好的替代方法吗?

    参考方案

    有没有办法诊断这种问题?

    您可以使用像async-profiler这样的可识别内核的Java事件探查器。

    这是对JDK 8的显示:

    Java 10(Centos)上的RandomAccessFile.setLength慢得多 - java

    对于JDK 10:

    Java 10(Centos)上的RandomAccessFile.setLength慢得多 - java

    这些配置文件证实了您的结论,即RandomAccessFile.setLength在JDK 8上使用ftruncate syscall,但在JDK 10上使用了更重的fallocate
    ftruncate确实非常快,因为它仅更新文件元数据,而fallocate实际上分配磁盘空间(如果是tmpfs,则分配物理内存)。

    进行此更改是为了在扩展文件大小以映射它时尝试修复JDK-8168628:SIGBUS。但是后来意识到这是个坏主意,并且此修复程序在JDK 11:JDK-8202261中进行了还原。

    是否有任何在Java上有效运行的解决方案或替代方案
    10点

    内部类sun.nio.ch.FileDispatcherImpl具有静态truncate0方法。它在后台使用ftruncate syscall。您可以通过Reflection调用它,请记住这是一个私有的不受支持的API。

    Class<?> c = Class.forName("sun.nio.ch.FileDispatcherImpl");
    Method m = c.getDeclaredMethod("truncate0", FileDescriptor.class, long.class);
    m.setAccessible(true);
    m.invoke(null, raf.getFD(), length);
    

    Spring Boot:java.time.Duration的默认序列化从字符串更改为数字 - java

    我们最近从Spring Boot 2.1.9升级到2.2.1,这导致我们的测试失败。调查导致结果,默认情况下java.time.Duration类型现在序列化为不同的序列。现在,我们将得到"PT15M",而不是在JSON消息中包含字符串"900.0"。 POJO定义如下所示@JsonProperty(required …

    Java:线程池如何将线程映射到可运行对象 - java

    试图绕过Java并发问题,并且很难理解线程池,线程以及它们正在执行的可运行“任务”之间的关系。如果我创建一个有10个线程的线程池,那么我是否必须将相同的任务传递给池中的每个线程,或者池化的线程实际上只是与任务无关的“工人无人机”可用于执行任何任务?无论哪种方式,Executor / ExecutorService如何将正确的任务分配给正确的线程? 参考方案 …

    JAVA:字节码和二进制有什么区别? - java

    java字节代码(已编译的语言,也称为目标代码)与机器代码(当前计算机的本机代码)之间有什么区别?我读过一些书,他们将字节码称为二进制指令,但我不知道为什么。 参考方案 字节码是独立于平台的,在Windows中运行的编译器编译的字节码仍将在linux / unix / mac中运行。机器代码是特定于平台的,如果在Windows x86中编译,则它将仅在Win…

    java:继承 - java

    有哪些替代继承的方法? java大神给出的解决方案 有效的Java:偏重于继承而不是继承。 (这实际上也来自“四人帮”)。他提出的理由是,如果扩展类未明确设计为继承,则继承会引起很多不正常的副作用。例如,对super.someMethod()的任何调用都可以引导您通过未知代码的意外路径。取而代之的是,持有对本来应该扩展的类的引用,然后委托给它。这是与Eric…

    Java:BigInteger,如何通过OutputStream编写它 - java

    我想将BigInteger写入文件。做这个的最好方式是什么。当然,我想从输入流中读取(使用程序,而不是人工)。我必须使用ObjectOutputStream还是有更好的方法?目的是使用尽可能少的字节。谢谢马丁 参考方案 Java序列化(ObjectOutputStream / ObjectInputStream)是将对象序列化为八位字节序列的一种通用方法。但…