关于 java 导致打开的文件太多?
About causing too many open files with java?
查看同事代码时,发现如下代码
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 数字不起作用?
我假设垃圾收集器是 运行,找到许多无法访问的 BufferedReader
对象并收集它们。这会导致底层流对象被最终确定...从而关闭它们。
要打破此代码,请将 BufferedReader
个对象添加到列表中,以便它们仍然可以访问。
这就是我认为将 7168 更改为 MAXINT 有效的原因。
当 JVM 启动时,它会使用相对较小的堆。 GC 期间发生的一件事是 JVM 决定是否需要调整堆的大小。所以这就是可能发生的事情:
JVM 从一个太小的堆开始,无法容纳 7168 个打开的文件 + BufferedReader 对象。 (请记住,后者中的每一个都可能有一个预分配的缓冲区!)
您开始打开文件。
在大约 N = 7168 - 2538 时,堆填满了所有 BufferedReader 对象 + FileInputStream 对象 + 来自 JVM 启动/预热的各种碎屑。
GC 运行,并导致(可能)收集/完成/关闭所有 BufferedReader 对象。
然后GC决定它需要扩大堆。您现在有足够的堆 space 来容纳比您的 ulimit 允许的更多的打开的 BufferedReader 对象。
您继续打开文件...然后达到打开文件限制。
这是一种可能的模式。
如果你真的想调查这个,我建议你打开 GC 日志记录,看看你是否可以将 lsof 报告的 FD 数量与 GC 运行相关联。
(您可以尝试在每个打开之间添加 sleep
调用,以便更容易获得 lsof 测量值。但这可能会以其他方式改变 JVM 行为...)
我没有确切的解释,但有一些额外的想法:"we"必须明白事情并不像表面上看起来那么简单。
关键是:有几个抽象层在起作用。有 JVM 和 JIT;然后是操作系统。
含义:考虑到这些抽象,天真 期望每个新的 BufferReader 直接导致另一个文件句柄是太过天真。如果 Linux 内核在这里启动,我不会感到惊讶; "tells" "yes, I opened that file; and read it for you; here is its content" 的 JVM。但是在 "reality" 中,Linux 内核知道这个文件自上次读取请求以来没有被触及也没有改变......
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
@Stephen C说的对,涉及GC
我创建了一个 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
查看同事代码时,发现如下代码
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 数字不起作用?
我假设垃圾收集器是 运行,找到许多无法访问的 BufferedReader
对象并收集它们。这会导致底层流对象被最终确定...从而关闭它们。
要打破此代码,请将 BufferedReader
个对象添加到列表中,以便它们仍然可以访问。
这就是我认为将 7168 更改为 MAXINT 有效的原因。
当 JVM 启动时,它会使用相对较小的堆。 GC 期间发生的一件事是 JVM 决定是否需要调整堆的大小。所以这就是可能发生的事情:
JVM 从一个太小的堆开始,无法容纳 7168 个打开的文件 + BufferedReader 对象。 (请记住,后者中的每一个都可能有一个预分配的缓冲区!)
您开始打开文件。
在大约 N = 7168 - 2538 时,堆填满了所有 BufferedReader 对象 + FileInputStream 对象 + 来自 JVM 启动/预热的各种碎屑。
GC 运行,并导致(可能)收集/完成/关闭所有 BufferedReader 对象。
然后GC决定它需要扩大堆。您现在有足够的堆 space 来容纳比您的 ulimit 允许的更多的打开的 BufferedReader 对象。
您继续打开文件...然后达到打开文件限制。
这是一种可能的模式。
如果你真的想调查这个,我建议你打开 GC 日志记录,看看你是否可以将 lsof 报告的 FD 数量与 GC 运行相关联。
(您可以尝试在每个打开之间添加 sleep
调用,以便更容易获得 lsof 测量值。但这可能会以其他方式改变 JVM 行为...)
我没有确切的解释,但有一些额外的想法:"we"必须明白事情并不像表面上看起来那么简单。
关键是:有几个抽象层在起作用。有 JVM 和 JIT;然后是操作系统。
含义:考虑到这些抽象,天真 期望每个新的 BufferReader 直接导致另一个文件句柄是太过天真。如果 Linux 内核在这里启动,我不会感到惊讶; "tells" "yes, I opened that file; and read it for you; here is its content" 的 JVM。但是在 "reality" 中,Linux 内核知道这个文件自上次读取请求以来没有被触及也没有改变......
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
@Stephen C说的对,涉及GC
我创建了一个 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