打开文件句柄太多

我正在开发一个庞大的遗留Java应用程序,有很多手写的东西,现在你可以让框架处理。

我现在面临的问题是我们的Solaris服务器上的文件句柄不足。 我想知道跟踪打开文件句柄的最佳方法是什么? 在哪里查看以及什么可能导致打开文件句柄用完?

我无法在Solaris下调试应用程序,只能在我的Windows开发环境中调试。 在Windows下分析打开的文件句柄是否合理?

我发现用于跟踪未关闭的文件句柄的一件好事是FindBugs:

http://findbugs.sourceforge.net/

它会检查很多东西,但其中最有用的是资源打开/关闭操作。 它是一个静态分析程序,运行在您的源代码上,它也可以作为eclipse插件使用。

在Windows上,您可以使用process explorer查看打开的文件句柄:

http://technet.microsoft.com/en-us/sysinternals/bb896653.aspx

在Solaris上,您可以使用“lsof”来监视打开的文件句柄

值得注意的是, 开放套接字也会消耗Unix系统上的文件句柄。 因此很可能是数据库连接池泄漏(例如打开数据库连接未被关闭并返回到池中)导致此问题 – 当然我在连接池泄漏之前已经看到此错误。

回答问题的第二部分:

什么可能导致打开文件句柄用完?

显然,打开很多文件,然后不关闭它们。

最简单的情况是,在关闭之前,对包含本机句柄(例如, FileInputStream )的任何对象的引用都会被丢弃,这意味着文件在对象完成之前保持打开状态。

另一个选择是对象存储在某个地方而不是关闭。 堆转储可能能够告诉你什么徘徊在哪里( jmapjhat包含在JDK中,或者你可以使用jvisualvm如果你想要一个GUI)。 您可能对寻找拥有FileDescriptor的对象感兴趣。

当我需要测试ic计数时,这个小脚本帮助我关注打开文件的数量。 如果在Linux上使用,那么对于Solaris你应该修补它(可能是:))

 #!/bin/bash COUNTER=0 HOW_MANY=0 MAX=0 # do not take care about COUNTER - just flag, shown should we continie or not while [ $COUNTER -lt 10 ]; do #run until process with passed pid alive if [ -r "/proc/$1" ]; then # count, how many files we have HOW_MANY=`/usr/sbin/lsof -p $1 | wc -l` #output for live monitoring echo `date +%H:%M:%S` $HOW_MANY # uncomment, if you want to save statistics #/usr/sbin/lsof -p $1 > ~/autocount/config_lsof_`echo $HOW_MANY`_`date +%H_%M_%S`.txt # look for max value if [ $MAX -lt $HOW_MANY ]; then let MAX=$HOW_MANY echo new max is $MAX fi # test every second. if you don`t need so frequenlty test - increase this value sleep 1 else echo max count is $MAX echo Process was finished let COUNTER=11 fi done 

你也可以尝试使用jvm ontion -Xverify:none – 它应该禁用jarvalidation(如果大多数打开的文件是jar …)。 对于通过未关闭的FileOutputStream泄漏,您可以使用findbug(上面提到过)或尝试查找文章如何修补标准java FileOutputStream / FileInputStream,您可以在其中查看,谁打开文件,并忘记关闭它们。 不幸的是,现在找不到这篇文章,但这是现有的:)还想想增加filelimit – 对于最新的* nix内核来说,处理超过1024 fd的问题不是问题。

这在你的情况下可能不实用,但是当我遇到与开放数据库连接有类似问题时我曾做过的事情是用我自己的方法覆盖“打开”function。 (方便的是我已经有了这个function,因为我们已经编写了自己的连接池。)在我的函数中,我在一个记录open的表中添加了一个条目。 我做了一个堆栈跟踪调用并保存了调用者的身份,以及调用的时间,我忘记了其他什么。 释放连接后,我删除了表条目。 然后我有一个屏幕,我们可以转储打开的条目列表。 然后,您可以查看时间戳,轻松查看哪些连接已经打开了不太长的时间,以及打开了哪些function。

由此我们能够快速找到打开连接并且无法关闭它们的几个function。

如果你有很多打开的文件句柄,那么当你在某个地方完成时,你可能无法关闭它们。 你说你已经检查了正确的try / finally块,但是我怀疑代码中的某个地方你错过了一个坏的,或者你有一个function,并且永远不会进入最后。 我想你每次打开文件时都可能正确地关闭,但是你同时打开了数百个文件。 如果是这种情况,我不确定你能做什么,除了重新设计一个严肃的程序来操作更少的文件,或者重新设计一个严肃的程序来排队你的文件访问。 (此时我添加了通常的“不知道您的应用程序的详细信息等)

我首先要求我的系统管理员获取该进程的所有打开文件描述符的列表。 不同的系统以不同的方式执行此操作:例如,Linux具有/proc/PID/fd目录。 我记得Solaris有一个命令(也许是pfiles ?)会做同样的事情 – 你的系统管理员应该知道它。

但是,除非您看到对同一文件的大量引用,否则fd列表不会对您有所帮助。 如果它是一个服务器进程,它可能有很多文件(和套接字)打开了一个原因。 解决问题的唯一方法是调整打开文件的系统限制 – 您还可以使用ulimit检查每用户限制,但在大多数当前安装中等于系统限制。

不是您的问题的直接答案,但这些问题可能是您的遗留代码中错误地释放文件资源的结果。 例如,如果您正在使用FileOutputsStream类,请确保在finally块中调用close方法,如下例所示:

 FileOutputsStream out = null; try { //You're file handling code } catch (IOException e) { //Handle } finally { if (out != null) { try { out.close(): } catch (IOException e) { } } } 

我会仔细检查Solaris盒子上的环境设置。 我相信默认情况下,Solaris每个进程只允许256个文件句柄。 对于服务器应用程序,特别是如果它在专用服务器上运行,这非常低。 图50或更多用于打开JRE和库JAR的描述符,然后每个传入请求和数据库查询至少有一个描述符,可能更多,你可以看到这不会削减严重服务器的芥末。

查看/etc/system文件,了解rlim_fd_currlim_fd_max的值,以查看系统设置的内容。 然后考虑这是否合理(您可以看到在服务器使用lsof命令运行时打开了多少文件描述符,理想情况下使用-p [进程ID]参数。

它当然可以给你一个想法。 由于它是Java,因此应该类似地实现文件打开/关闭机制(除非其中一个JVM实现不正确)。 我建议在Windows上使用File Monitor 。

谷歌的一个名为filemon的应用程序来自系统内部。

顺便说一下,为了跟踪这个问题,你可以使用像aspectj这样的东西来记录打开和关闭文件的所有调用,并记录它们发生的位置。

这是一种有助于查找未封闭资源的编码模式。 它关闭了资源,并在日志中抱怨问题。

 class { boolean closed = false; File file; close() { closed = true; file.close(); } finalize() { if (!closed) { log error "OI! YOU FORGOT TO CLOSE A FILE!" file.close(); } } 

在忽略错误的try-catch块中包含上面的file.close()调用。

此外,Java 7还有一个新的“资源尝试”function,可以自动关闭资源。