Java IOException“打开的文件过多”

Java IOException "Too many open files"

提问人:dpsthree 提问时间:11/27/2010 最后编辑:dpsthree 更新时间:1/20/2022 访问量:131976

问:

我正在对多个文件进行一些文件 I/O(写入 19 个文件,碰巧如此)。在给他们写了几百次之后,我得到了 Java : .但我实际上一次只打开了几个文件。这里有什么问题?我可以验证写入是否成功。IOExceptionToo many open files

java 文件-io

评论

2赞 James Black 11/27/2010
在将打开文件的函数中添加注释,并验证您是否只打开每个文件一次。
0赞 robert 11/27/2010
您如何知道此错误是由于这 19 个错误之一而发生的?Java 本身或您正在使用的框架可能正在打开文件。
6赞 harschware 11/27/2010
发布您的代码。也许你正在一个循环中打开它们,所以它反复发生。
4赞 Adrian Pronk 11/27/2010
如果您不自己关闭文件,而只是将其留给垃圾收集器为您清理,那么这将需要很长时间才能发生。与此同时,您正忙于打开更多文件,在不知不觉中,您已经达到了极限。

答:

3赞 Falmarri 11/27/2010 #1

在打开新文件描述符之前,您显然没有关闭文件描述符。您使用的是 Windows 还是 Linux?

评论

2赞 James Black 11/27/2010
如果他确实打开了 19 个文件,并且有一个带有文件描述符的数组,那么他应该没问题,但我认为他实际上打开的比他想象的要多。
0赞 Sanjay T. Sharma 11/27/2010
AFAIK,Windows没有这个问题;例如,在我的 XP SP3 上,我能够毫不费力地遍历 CXF(大约 15K)文件的整个源代码树,而无需关闭任何打开的文件。
4赞 Falmarri 11/30/2010
我保证 Windows 有文件描述符限制。它可能默认为 65000
62赞 Stephen C 11/27/2010 #2

在 Linux 和其他类 UNIX/UNIX 平台上,操作系统对进程在任何给定时间可能具有的打开文件描述符的数量进行了限制。在过去,这个限制曾经是硬连线的 1,而且相对较小。如今,它要大得多(数百/数千),并且受每个进程的“软”可配置资源限制的约束。(查找内置的 shell ...ulimit

您的 Java 应用程序必须超过每个进程的文件描述符限制。

您说您打开了 19 个文件,几百次后您收到 IOException 说“打开的文件太多”。现在,此特定异常仅在请求新文件描述符时才会发生;即当您打开文件(或管道或套接字)时。您可以通过打印 IOException 的堆栈跟踪来验证这一点。

除非您的应用程序以较小的资源限制(这似乎不太可能)运行,否则它必须重复打开文件/套接字/管道,并且无法关闭它们。找出为什么会发生这种情况,您应该能够弄清楚该怎么做。

仅供参考,以下模式是写入文件的安全方法,可以保证不会泄漏文件描述符。

Writer w = new FileWriter(...);
try {
    // write stuff to the file
} finally {
    try {
        w.close();
    } catch (IOException ex) {
        // Log error writing file and bail out.
    }
}

1 - 硬连线,如编译到内核中。更改可用 fd 插槽的数量需要重新编译...并可能导致可用于其他事情的内存减少。在Unix通常运行在16位机器上的日子里,这些东西真的很重要。

更新

Java 7 的方式更简洁:

try (Writer w = new FileWriter(...)) {
    // write stuff to the file
} // the `w` resource is automatically closed 

更新 2

显然,在尝试运行外部程序时,您还会遇到“打开的文件过多”。基本原因如上所述。但是,您遇到这种情况的原因是 JVM 正在尝试创建“管道”文件描述符,这些描述符将连接到外部应用程序的标准输入/输出/错误。exec(...)

评论

7赞 yegor256 7/26/2012
我建议使用 IOUtils.closeQuietly()
16赞 Stephen C 7/26/2012
我也会用它。但我不建议添加该依赖项只是为了获得您可以在 2 分钟内自己编写的方法。
2赞 Stephen C 12/2/2016
事实上,在 2016 年,我强烈建议使用 try with resources;即“Java 7 方式”。如果您仍在使用 Java 6 或更早版本,则应进行升级。Java 6 已经停止维护 4 年了。
2赞 computingfreak 4/23/2018
当我在 IOUtils 文档页面上读到有关 closeQuietly(Closeable...closeables) 方法,它说,"Deprecated. As of 2.6 removed without replacement. Please use the try-with-resources statement or handle suppressed exceptions manually."
0赞 Stephen C 1/30/2019
@computingfreak - 是的。从 Java 7 开始,您不再需要显式使用。弃用只是试图以更现代的方式做事......并且更快乐。请参阅我的第一个更新。close()IOUtils
-7赞 Lenik 1/19/2012 #3

最近,我有一个程序批处理文件,我当然已经关闭了循环中的每个文件,但错误仍然存在。

后来,我通过每数百个文件热切地收集垃圾解决了这个问题:

int index;
while () {
    try {
        // do with outputStream...
    } finally {
        out.close();
    }
    if (index++ % 100 = 0)
        System.gc();
}

评论

8赞 Stephen C 2/7/2012
对不起,你错了。您可能认为您显式关闭了所有流,但是在程序中的某个位置存在导致流未关闭的执行路径。运行 GC 会导致“丢失”流最终确定。流终结调用 (IIRC)。this.close()
0赞 Stefan Reich 12/4/2017
很高兴知道 gc() 关闭了流。
2赞 user207421 2/12/2018
@StefanReich 事实并非如此。它关闭文件描述符。流将保持未刷新状态。
8赞 pk10 12/6/2013 #4

对于 UNIX:

正如 Stephen C 所建议的,将最大文件描述符值更改为更高的值可以避免此问题。

尝试查看您当前的文件描述符容量:

   $ ulimit -n

然后根据您的要求更改限制。

   $ ulimit -n <value>

请注意,这只是更改了当前 shell 和任何子/后代进程中的限制。要使更改“坚持”,您需要将其放入相关的 shell 脚本或初始化文件中。

评论

3赞 Stephen C 4/23/2018
它只会避免该问题,直到您的应用程序达到新的上限。IMO,这不是一个好的解决方案。
2赞 user458577 9/25/2014 #5

尽管在大多数一般情况下,错误很明显是文件句柄尚未关闭,但我刚刚在 Linux 上遇到了一个带有 JDK7 的实例......足以在这里解释。

该程序打开了 FileOutputStream (fos)、BufferedOutputStream (bos) 和 DataOutputStream (dos)。写入 dataoutputstream 后,dos 被关闭,我认为一切正常。

但是,在内部,dos 尝试刷新 bos,这返回了磁盘已满错误。该异常被 DataOutputStream 吃掉,因此底层 bos 未关闭,因此 fos 仍处于打开状态。

在后来的阶段,该文件从(带有.tmp的东西)重命名为其真实名称。因此,java 文件描述符跟踪器失去了对原始 .tmp 的跟踪,但它仍然处于打开状态!

为了解决这个问题,我必须首先自己刷新 DataOutputStream,检索 IOException 并自己关闭 FileOutputStream。

我希望这对某人有所帮助。

评论

0赞 user207421 2/12/2018
这没有任何意义。 不吞下异常。 确实如此,但不会以压制收盘价的方式。关于“Java 文件描述符跟踪器”的部分,无论它们是什么,“失去原始 .tmp 的跟踪”都没有可确定的意义。DataOutputStreamFilterOutputStream.close()
0赞 Joshua S 1/20/2022 #6

如果在自动化测试中看到此情况:最好在测试运行之间正确关闭所有文件。

如果您不确定您打开了哪些文件,那么一个好的起点是引发异常的“打开”调用!😄

如果文件句柄的父对象处于活动状态,则可以在父句柄上添加一个方法,该方法在文件句柄上调用 close。并在测试之间调用。finalizeSystem.gc()