关于使用java导致过多的打开文件?

时间:2017-04-13 14:31:05

标签: java ulimit

审核同事的代码时,找到以下代码

    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 - &gt; 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数不起作用?

3 个答案:

答案 0 :(得分:6)

我认为垃圾收集器正在运行,发现许多无法访问的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行为......)

答案 1 :(得分:0)

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

重点是:有几层抽象可以发挥作用。有JVM和JIT;然后是那些操作系统。

含义:给定这些抽象,只是太天真期望每个新的BufferReader直接导致另一个文件句柄。如果Linux内核在这里开始,我不会感到惊讶;只是&#34;告诉&#34; JVM,是的,我打开了那个文件;并为你阅读;这是它的内容&#34;。但是在&#34; reality&#34;中,Linux内核理解这个文件没有被触及,并且自上次读取请求以来没有改变...

答案 2 :(得分:0)

  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参与其中。

  3. 我创建了一个MyBufferedReader extends 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