打开的文件句柄过多

11

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

我现在面临的问题是我们在Solaris服务器上运行时会出现文件句柄不足的情况。我想知道跟踪打开文件句柄的最佳方法是什么?需要查看哪些内容以及可能导致文件句柄不足的原因是什么?

我不能在Solaris下调试应用程序,只能在我的Windows开发环境中。在Windows下分析打开的文件句柄甚至可行吗?


1
使用“lsof -p PID”命令,最常见的条目是以下内容:java 19157 dev 131u unix 105,98572 0t829 55050244 /devices/pseudo/tl@0:ticots->(socketpair: 0x1810c) (0x300199eed50)有任何想法吗?如何解决? - dlinsin
12个回答

8

谢谢!我使用了lsof,但是有很多东西在进行中,我不知道如何将lsof结果缩小到相关和不感兴趣的内容。 - dlinsin
1
Windows的结果不能推断到*nix系统上。它们有不同的文件打开机制。 - St.Shadow

8

我发现用FindBugs可以很好地追踪未关闭的文件句柄:

http://findbugs.sourceforge.net/

它检查了许多问题,但其中最有用的之一是资源打开/关闭操作。它是一个静态分析程序,运行在您的源代码上,并且也可作为eclipse插件使用。


作为个人证言,我曾经遇到过与OP类似的问题(我的应用程序抛出异常,因为它无法打开更多文件,因为我有太多打开的文件描述符)。通过运行代码查找错误(findbugs)帮助识别了所有未关闭文件的位置。问题解决了! - tth
是的,它曾经帮助我找到了一大堆没有在适当的finally块中调用close()的地方。 - Benj
虽然它没有直接解决我的问题,但是这是一个很好的提示! - dlinsin

3
这个小脚本可以帮助我在测试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选项-Xverify:none进行游戏,它应该禁用jar验证(如果大多数打开的文件是jar文件...)。 对于通过未关闭的FileOutputStream泄漏,您可以使用findbug(上面有介绍),或者尝试找到如何修补标准java FileOutputStream / FileInputStream的文章,在那里您可以看到谁打开了文件,并忘记关闭它们。不幸的是,现在无法找到这篇文章,但它确实存在 :) 还要考虑增加文件限制-对于最新的*nix内核来说,处理超过1024个fd不是问题。

2
回答问题的第二部分:
“什么会导致打开的文件句柄用尽?”
显然,打开了大量的文件,但没有关闭它们。
最简单的情况是在关闭之前丢弃了持有本机句柄对象(例如FileInputStream)的引用,这意味着文件将保持打开状态,直到对象被终结。
另一个选项是对象被存储在某个地方而没有关闭。堆转储可能能告诉你什么东西停留在哪里(JDK 中包括 jmapjhat,或者如果你想要一个 GUI,可以使用 jvisualvm)。你可能有兴趣查找拥有 FileDescriptor 的对象。

2
这在你的情况下可能不太实用,但我曾经遇到过与打开数据库连接类似的问题,那时我覆盖了“open”函数。 (方便的是,我们已经编写了自己的连接池并拥有此功能)。在我的函数中,我添加了一个表项来记录打开的连接。我进行了一次堆栈跟踪调用,并保存了调用者的标识,以及调用时间和其他我忘记的内容。当连接被释放时,我删除了表项。然后我有一个屏幕,可以将打开的条目列表转储出来。您可以查看时间戳,轻松地看到哪些连接已经打开了不可能的时间,并且哪些功能已经打开了这些连接。
从这里,我们能够快速追踪到几个打开连接并未关闭的功能。
如果您有很多打开的文件句柄,则很可能是在某个地方没有关闭它们。您说您已经检查了正确的try/finally块,但我怀疑在代码中的某个地方您可能错过了一个坏的块,或者您有一个函数处于挂起状态,永远无法到达finally。我想这也有可能是真的,每次打开文件时都会进行适当的关闭,但您同时打开了数百个文件。如果是这种情况,除了严重的程序重新设计以操作较少的文件或严重的程序重新设计以排队访问文件外,我不确定您能做什么。(此时我添加了通常的“不知道您的应用程序等细节”)。

2

需要记住的是,在Unix系统中,打开的套接字也会消耗文件句柄。因此,可能是像数据库连接池泄漏(例如未关闭和返回到池中的打开数据库连接)这样的问题导致了这个问题 - 我确实曾经看到过由于连接池泄漏而引起的这个错误。


1

虽然这不是直接回答你的问题,但这些问题可能是由于在您的旧代码中错误地释放文件资源导致的。例如,如果您正在使用FileOutputsStream类,请确保在finally块中调用close方法,如下例所示:

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

他说的听起来像是文件句柄从未被释放。 - ChadNC
谢谢您的一般建议,但我已经搜索了所有java.io.*的出现,并确保它们在try-catch-finally块中。 - dlinsin

1
我会首先向我的系统管理员询问该进程的所有打开文件描述符列表。不同的系统以不同的方式执行:例如,Linux有/proc/PID/fd目录。我记得Solaris有一个命令(也许是pfiles?)可以做同样的事情 - 您的系统管理员应该知道它。
但是,除非您看到了对同一文件的许多引用,否则fd列表将无法帮助您。如果这是服务器进程,它可能会因为某种原因打开很多文件(和套接字)。解决问题的唯一方法是调整打开文件的系统限制 - 您还可以使用ulimit检查每个用户的限制,但在大多数当前安装中,这等于系统限制。

1

我建议您仔细检查Solaris服务器的环境设置。我认为,默认情况下,Solaris每个进程只允许256个文件句柄。对于服务器应用程序来说,特别是在专用服务器上运行时,这非常低。考虑到需要打开JRE和库JAR的50个或更多描述符,以及每个传入请求和数据库查询至少一个描述符,可能还需要更多,您可以看到这对于严肃的服务器来说根本不够。

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


0

这是一种编程模式,可帮助找到未关闭的资源。它会关闭资源,并在日志中报告问题。

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();
    }
}

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

此外,Java 7 还有一个新的“try-with-resource”功能,可以自动关闭资源。


使用finalize()是非常糟糕的设计。http://www.informit.com/articles/article.aspx?p=1216151&seqNum=7 - To Kra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接