【问题标题】:About causing too many open files with java?关于用java打开文件太多?
【发布时间】:2017-09-09 17:43:55
【问题描述】:

查看同事的代码,发现代码如下

    BufferedReader br = new BufferedReader(new FileReader(PATH + fileName));
    //...

只是读取一个文件并将这些行连接为一行,但我没有找到任何关闭代码,所以我认为它应该导致资源泄漏,最后导致too many open files error,所以为了证明这一点,我写了一个测试

for (int i = 0; i < 7168; i++) { // ulimit -n ==> 7168
    BufferedReader br = new BufferedReader(new FileReader("src/main/resources/privateKey/foo.pem"));
    System.out.println(br.readLine());
}
System.in.read();

很奇怪,一切正常,没有抛出预期的异常。

并在命令行中查看真正打开的文件

➜  ~ lsof -p 16276 | grep 'foo.pem' | wc -l
    2538

为什么只有 2538,而不是 7168?

那怎么了?如何导致too many open files error


按照@GhostCat的建议,改7168 --> Integer.MAX_VALUE,这次是造成

java.io.FileNotFoundException: src/main/resources/privateKey/foo.pem (Too many open files in system)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)

当我是27436,在这种情况下检查命令行中真正打开的文件是

➜  ~ lsof | grep foo.pem | wc -l
    7275

但是剩下的文件在哪里(27346 - 7275)?以及为什么 ulimit number 不起作用?

【问题讨论】:

  • 我会使用while(true) 循环开始这样的实验......并且可能会将该计数器打印到每个文件中......并检查磁盘上发生的情况。
  • 谢谢!请查看我的其他信息
  • 除此之外:您可能忽略了我的建议:我会尝试打开不同的文件以写入,并将不同的内容写入每个文件。除非我的回答完全是虚假的,否则如果您发现您的操作系统能够以某种方式“优化”对同一文件的许多读取请求,我不会感到惊讶。但是将不同的东西写入不同的文件是不能轻易优化的。让我知道结果是什么;-)

标签: java ulimit


【解决方案1】:

我假设垃圾收集器正在运行,发现许多无法访问的BufferedReader 对象并收集它们。这会导致底层流对象被最终确定......这会关闭它们。

要中断此代码,请将BufferedReader 对象添加到列表中,以便它们保持可访问性。


这就是我认为将 7168 更改为 MAXINT 有效的原因。

当 JVM 启动时,它将使用一个相对较小的堆。在 GC 期间发生的一件事是 JVM 决定是否需要调整堆大小。所以这就是可能发生的事情

  • JVM 开始时堆太小,无法容纳 7168 个打开的文件 + BufferedReader 对象。 (请记住,后者可能都有一个预先分配的缓冲区!)

  • 您开始打开文件。

  • 大约在 N = 7168 - 2538 时,堆被所有 BufferedReader 对象 + FileInputStream 对象 + 来自 JVM 启动/预热的各种碎屑填满。

  • GC 运行,并导致(可能)所有 BufferedReader 对象被收集/完成/关闭。

  • 然后 GC 决定它需要扩展堆。您现在有足够的堆空间来容纳比 ulimit 允许的更多打开的 BufferedReader 对象。

  • 您继续打开文件...然后达到打开文件限制。

这是一种可能的模式。


如果您真的想对此进行调查,我建议您打开 GC 日志记录,看看您是否可以将 lsof 报告的 FD 数量与 GC 运行相关联。

(您可以尝试在每次打开之间添加 sleep 调用,以便更容易获得 lsof 测量值。但这可能会以其他方式改变 JVM 行为...)

【讨论】:

  • 我不这么认为,因为每次执行lsof 命令都会得到相同的结果。如果涉及 gc,结果会发生变化,即值会变小直到 0
  • 我同意斯蒂芬的看法。但是你为什么不测试它:覆盖 BufferedReader,覆盖 finalize,看看会发生什么......?
  • @zhguuowei - 你假设 GC 一直在运行。事实上,它只在需要时运行;例如当“空间”填满时。所以你提议的行为不会发生。
【解决方案2】:

我没有确切的解释,但有一些额外的想法:“我们”必须明白事情并不像表面上看起来那么简单。

关键是:有几个抽象层在起作用。有 JVM 和 JIT;然后是那些下面的操作系统。

含义:考虑到这些抽象,期望每个新的 BufferReader 直接指向另一个文件句柄实在是太天真了。如果 Linux 内核出现在这里,我不会感到惊讶。并且只是“告诉”JVM“是的,我打开了该文件;并为您阅读;这是它的内容”。但在“现实”中,Linux 内核知道该文件自上次读取请求以来没有被触及,也没有改变......

【讨论】:

    【解决方案3】:
    1. jvm 隐式更新 ulimit 值

      String [] cmdArray = {"sh","-c","ulimit -n"};
      Process p = Runtime.getRuntime().exec(cmdArray);
      BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
      System.out.println(in.readLine()); //it is 10240 not 7168
      
    2. @Stephen C 是对的,涉及到 GC。

    我创建了一个 MyBufferedReader 扩展了 BufferedRead 并覆盖了 finalize 方法

    @Override
    protected void finalize() throws Throwable {
        System.out.printf("Thread: %s finalize it and total: %d %n",Thread.currentThread().getName(),count.getAndAdd(1));
    }
    

    得到以下信息

    Thread: Finalizer finalize it and total: 9410 
    

    在命令行中

    ➜  ~ lsof -p 5309 | grep 'taicredit_private_key_pkcs8' | wc -l
         830
    

    9410 + 830 = 10240

    【讨论】:

      猜你喜欢
      • 2011-06-29
      • 2011-09-10
      • 2011-05-16
      • 1970-01-01
      • 1970-01-01
      • 2012-07-05
      • 1970-01-01
      相关资源
      最近更新 更多