【问题标题】:Too many open file handles打开的文件句柄太多
【发布时间】:2009-11-02 13:24:07
【问题描述】:

我正在开发一个巨大的遗留 Java 应用程序,其中包含大量手写内容,现在您可以让框架处理这些内容。

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

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

【问题讨论】:

  • 使用 lsof -p PID,最常见的条目如下: java 19157 dev 131u unix 105,98572 0t829 55050244 /devices/pseudo/tl@0:ticots->(socketpair: 0x1810c) (0x300199eed50) 知道这意味着什么以及如何与之抗争吗?

标签: java file io solaris


【解决方案1】:

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

http://findbugs.sourceforge.net/

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

【讨论】:

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

在 Windows 上,您可以使用进程资源管理器查看打开的文件句柄:

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

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

【讨论】:

  • 谢谢!我使用了 lsof,不幸的是,发生了很多事情,我真的不知道如何将 lsof 结果缩小到相关的和不感兴趣的。
  • 不应将来自 Windows 的结果外推到 *nix 系统。它们有不同的文件打开机制。
【解决方案3】:

回答问题的第二部分:

什么会导致打开的文件句柄用完?

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

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

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

【讨论】:

    【解决方案4】:

    当我需要测试 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 - 它应该禁用 jar 验证(如果大多数打开的文件是 jars...)。 对于通过未关闭的 FileOutputStream 进行的泄漏,您可以使用 findbug(上面指导过)或尝试查找文章如何修补标准 java FileOutputStream/FileInputStream ,您可以在其中看到谁打开了文件,而忘记了关闭它们。不幸的是,现在找不到这篇文章,但这是存在的 :) 还要考虑增加文件限制 - 对于最新的 *nix 内核,处理超过 1024 fd 不是问题。

    【讨论】:

      【解决方案5】:

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

      据此,我们能够快速追踪打开连接和未能关闭连接的几个函数。

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

      【讨论】:

        【解决方案6】:

        值得记住的是,open sockets 也会消耗 Unix 系统上的文件句柄。所以很可能是数据库连接池泄漏(例如,打开的数据库连接没有被关闭并返回到池中)导致这个问题 - 当然我之前已经看到这个错误是由连接池泄漏引起的。

        【讨论】:

          【解决方案7】:

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

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

          【讨论】:

            【解决方案8】:

            不是您问题的直接答案,但这些问题可能是由于在您的旧代码中错误地释放文件资源造成的。例如,如果您使用 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) { }
              }
            }
            

            【讨论】:

            • 他说了什么。听起来文件句柄永远不会被释放。
            • 感谢您的一般建议,但我已经搜索了所有出现的 java.io.* 并确保它们位于 try-catch-finally 块中。
            【解决方案9】:

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

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

            【讨论】:

              【解决方案10】:

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

              【讨论】:

                【解决方案11】:

                Google 从系统内部获取名为 filemon 的应用。

                顺便说一句,要追踪这一点,您可以使用 aspectj 之类的东西来记录所有打开和关闭文件的调用并记录它们发生的位置。

                【讨论】:

                  【解决方案12】:

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

                  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”功能,可以自动关闭资源。

                  【讨论】:

                  猜你喜欢
                  • 2014-09-17
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-05-21
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多