【问题标题】:I/O performance of multiple JVM (Windows 7 affected, Linux works)多个 JVM 的 I/O 性能(Windows 7 受影响,Linux 工作)
【发布时间】:2011-11-23 20:00:49
【问题描述】:

我有一个程序可以创建一个大小约为 50MB 的文件。在此过程中,程序经常重写文件的某些部分并强制将更改写入磁盘(大约 100 次)。它通过 fc.read(...)、fc.write(...) 和 fc.force(...) 使用 FileChannel 和直接 ByteBuffers。

新文本:

我现在对这个问题有了更好的看法。 问题似乎是我使用三个不同的 JVM 来修改一个文件(一个创建它,另外两个(从第一个启动)写入它)。在下一个 JVM 启动之前,每个 JVM 都会正确关闭文件。 问题是该文件的 fc.write() 成本偶尔会在第三个 JVM 中达到最高值(大约是正常成本的 100 倍)。也就是说,所有的写操作都同样慢,不仅仅是一个挂得很长的。 有趣的是,帮助实现这一点的一种方法是在 JVM 启动之间插入延迟(2 秒)。没有延迟,写总是很慢,有延迟,大约每秒写一次。

我还发现了这个Stackoverflow: How to unmap a file from memory mapped using FileChannel in java?,它描述了我没有使用的映射文件的问题。

我怀疑可能发生的事情: 当我调用 close() 时,Java 并没有完全释放文件句柄。当下一个 JVM 启动时,Java(或 Windows)识别对该文件的并发访问并为该文件安装一些昂贵的并发处理程序,这使得写入成本很高。 这有意义吗?

问题出现在 Windows 7(Java 6 和 7,在两台机器上测试),但在 Linux (SuSE 11.3 64) 下没有。

旧文本:

问题: 从 Eclipse 或控制台作为 JUnit 测试工具启动程序工作正常,大约需要 3 秒。 通过 ant 任务启动程序(或通过 JUnit 使用 ProcessBuilder 启动单独的 JVM)将同一任务的程序减慢到 70-80 秒(因子 20-30)。

使用 -Xprof 显示“force0”和“pwrite”的使用率从 34.1%(76+20 次)飙升至 97.3%(3587+2913+751 次): 快跑:

27.0%     0  +    76    sun.nio.ch.FileChannelImpl.force0
 7.1%     0  +    20    sun.nio.ch.FileDispatcher.pwrite0
[..]

慢跑:

    Interpreted + native   Method                        
48.1%     0  +  3587    sun.nio.ch.FileDispatcher.pwrite0
39.1%     0  +  2913    sun.nio.ch.FileChannelImpl.force0
[..]
     Stub + native   Method                        
10.1%     0  +   751    sun.nio.ch.FileDispatcher.pwrite0
[..]

GC 和编译可以忽略不计。

更多事实:

没有其他方法显示 -Xprof 输出有显着变化。

  • 要么快要么很慢,从不介于两者之间。
  • 内存没问题,所有测试机至少8GB,进程使用
  • 重启机器没有帮助
  • 切换病毒扫描程序和类似的东西没有影响
  • 当进程缓慢时,几乎没有 CPU 使用率
  • 从普通 JVM 运行它从不
  • 在从第一个 JVM(通过 ProcessBuilder 或作为 ant-task)启动的 JVM 中运行它时,它一直很慢
  • 所有 JVM 都完全相同。我通过 RuntimeMXBean RuntimemxBean = ManagementFactory.getRuntimeMXBean(); 输出 System.getProperty("java.home") 和 JVM 选项;列表参数 = RuntimemxBean.getInputArguments();
  • 我在两台 Windows7 64bit、Java 7u2、Java 6u26 和 JRockit 的机器上进行了测试,虽然机器的硬件不同,但结果非常相似。
  • 我也在 E​​clipse(命令行 ant)外部对其进行了测试,但没有区别。
  • 整个程序都是我自己写的,只是读写这个文件,没有用到其他库,尤其是没有本地库。 -

还有一些我只是拒绝相信没有任何意义的可怕事实:

  • 有时(很少)删除所有类文件并重建项目会有所帮助。程序(嵌套版本)运行一到两次,然后又变得非常慢。
  • 安装新的 JVM 总是有帮助的(每一次!),这样(嵌套的)程序至少可以快速运行一次!安装 JDK 算作两次,因为 JDK-jre 和 JRE-jre 都至少可以正常工作一次。重装 JVM 无济于事。重启也不行。我还没有尝试删除/重新启动/重新安装...
  • 这是我设法为嵌套程序获得快速程序运行时的仅有的两种方法。

问题:

  • 什么可能导致嵌套 JVM 的性能下降?
  • 这些方法到底有什么作用(pwrite0/force0)? -

【问题讨论】:

    标签: performance io jvm nested


    【解决方案1】:

    您是否使用本地磁盘进行所有测试(而不是任何网络共享)?

    你能用 ram 驱动器设置 Windows 来存储数据吗?当 JVM 终止时,默认情况下它的文件句柄将被关闭,但您可能会看到将数据刷新到磁盘。当你覆盖大量数据时,旧版本的数据被丢弃,可能不会导致磁盘 IO。关闭文件的行为可能会使 Windows 内核隐式地将数据刷新到磁盘。因此,使用 ram 驱动器可以让您确认他们的自磁盘 IO 时间已从您的统计数据中删除。

    为 Windows 找到一个工具,它允许您强制内核将所有缓冲区刷新到磁盘,在 JVM 运行之间使用它,看看当时需要多长时间。

    但我猜你在尝试管理磁盘块缓冲区缓存时遇到了进程需求和内核需求的一些迭代。在 linux 中有一个类似“/sbin/blockdev --flushbufs”的工具可以做到这一点。

    FWIW

    "pwrite" 是一个 Linux/Unix API,用于允许并发写入文件描述符(这将是用于 JVM 的最佳内核系统调用 API,我认为 Win32 API 已经为相同类型的使用提供了共享进程中线程之间的文件句柄,但由于 Sun 拥有 Unix 传统,因此以 Unix 方式命名)。谷歌“pwrite(2)”了解更多关于这个 API 的信息。

    “force”我猜这是文件系统同步,这意味着该进程正在请求内核将未写入的数据(当前位于磁盘块缓冲区缓存中)刷新到磁盘上的文件中(例如之前需要您关闭了计算机)。此操作将随着时间的推移自动发生,但事务系统需要知道先前写入(使用 pwrite)的数据何时实际到达物理磁盘并被存储。因为其他一些磁盘 IO 依赖于知道这一点,例如事务检查点。

    【讨论】:

      【解决方案2】:

      可以帮助您确保将FileChannel 明确设置为null。然后在程序结束时调用System.runFinalization()System.gc()。您可能需要不止 1 个电话。

      System.runFinalizersOnExit(true) 也可能有帮助,但它已被弃用,因此您必须处理编译器警告。

      【讨论】:

        猜你喜欢
        • 2020-04-05
        • 2020-01-24
        • 2012-08-06
        • 1970-01-01
        • 1970-01-01
        • 2021-04-20
        • 1970-01-01
        • 2010-11-10
        • 2011-05-07
        相关资源
        最近更新 更多