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

zhu*_*wei 5 java ulimit

当审查同事的代码时,发现下面的代码

    BufferedReader br = new BufferedReader(new FileReader(PATH + fileName));
    //...
Run Code Online (Sandbox Code Playgroud)

只是读取一个文件并将这些行连接成一行,但我没有发现任何密切的代码,所以我认为它应该导致资源泄漏,最后导致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();
Run Code Online (Sandbox Code Playgroud)

很奇怪,一切都好,不会抛出预期的异常.

并在命令行中检查实际打开的文件

?  ~ lsof -p 16276 | grep 'foo.pem' | wc -l
    2538
Run Code Online (Sandbox Code Playgroud)

为什么只有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)
Run Code Online (Sandbox Code Playgroud)

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

?  ~ lsof | grep foo.pem | wc -l
    7275
Run Code Online (Sandbox Code Playgroud)

但在哪里留下文件(27346 - 7275)?为什么ulimit号不起作用?

Ste*_*n C 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行为......)