1

介绍

我在网上找到了大量关于打开文件过多异常的信息,但我无法解决这个奇怪的情况。正如我所读到的,当超过操作系统中定义的进程打开的文件描述符的数量时,将引发异常。这些文件的性质是多种多样的。文件可以是套接字、文档等。而且我已经找到了打开在我的 Java 应用程序中实现的文件的可靠且安全的方法。

该应用程序是一个使用Boilerpipe算法下载网页的短程序。这样我就得到了那个网站最具代表性的内容。然后,我以适当的格式(TREC 格式)将其写入磁盘。这些网站的 URL 取自我使用JDBC 连接器访问的 MySQL 数据库。

所以,我认为可以从三个不同的地方抛出异常:

  • 连接到数据库
  • 与网站的 HTTP 连接
  • 打开和写入文件

虽然,正如我所说,我认为我使用正确的方式打开和写入这些文件。

问题

有数千个 URL 需要处理,一段时间后抛出异常(这也使得调试变得非常困难......)。我不知道这是否重要,但 URL 分为不同的类别,我运行程序的不同实例以加快整个过程。类别不重叠,所以不应该有任何问题。

代码

为了使其更具可读性,我将仅展示简化的代码的这三个部分:

  1. 数据库访问

    // Connect to database
    Connection dbconn = null;
    
    try {
        String dbUrl = "jdbc:mysql://" + dbServer + "/" + dbName;
        Class.forName ("com.mysql.jdbc.Driver").newInstance ();
        dbconn = DriverManager.getConnection(dbUrl, dbUser, dbPass);
        System.out.println ("Database connection established");
    } catch (Exception e) {
        e.printStackTrace();
        System.err.println ("Cannot connect to database server");
        System.exit(-1);
    }
    
    System.out.println("  Downloading category: " + category);                  
    
    Statement s = null;
    try {
        s = dbconn.createStatement();
    } catch (SQLException e) {
        System.err.println ("Error on creating the statement");
        System.exit(-1);
        e.printStackTrace();
    }
    
    String q = "SELECT resource,topic FROM " + 
            "content_links " + 
            "WHERE topic LIKE 'Top/" + category + "%';";
    
    try {
        s.executeQuery(q);
    } catch(Exception e) {
        System.err.println ("Error on executing the SQL statement");
        System.exit(-1);
        e.printStackTrace();
    }
    
    ResultSet rs = null;
    try {
        rs = s.getResultSet ();
    } catch (SQLException e) {
        System.err.println ("Error on getting the result set");
        System.exit(-1);
        e.printStackTrace();
    }
    
    
    int count = 0, webError = 0;
    
    // work with the result set
    try {
        while (rs.next ()) {
    
            // MAIN LOOP
        }
    
    } catch (SQLException e) {
        System.err.println ("Error on getting next item");
        System.exit(-1);
        e.printStackTrace();
    }
    
    // Close connection to database
    if (dbconn != null) {
        try {
            dbconn.close ();
            System.out.println ("  Database connection terminated");
        } catch (Exception e) { /* ignore close errors */ }
    }
    
  2. HTTP 连接,提取站点的标题和锅炉过滤器

    try {
    
        String title = "";
        org.jsoup.nodes.Document doc = Jsoup.connect(urlVal).get();
    
        for (Element element : doc.select("*")) {
            if (element.tagName().equalsIgnoreCase("title")) {
                title = element.text();
            }
            if (!element.hasText() && element.isBlock()) {
                element.remove();
            }
        }
    
        String contents = "";
        contents = NumWordsRulesExtractor.INSTANCE.getText(doc.text());                                         
        storeFile(id, urlVal, catVal, title, contents);
                }
    } catch (BoilerpipeProcessingException e) {
        System.err.println("Connection failed to: " + urlVal);
    } catch (MalformedURLException e1) {
        System.err.println("Malformed URL: " + urlVal);
    } catch(Exception e2) {
        System.err.println("Exception: " + e2.getMessage());
        e2.getStackTrace();
    }
    
  3. 写入文件

    private static void storeFile(String id, String url, String cat, String title, String contents) {
    BufferedWriter out = null;
    try {
        out = new BufferedWriter(
                new OutputStreamWriter(
                new FileOutputStream(
                new File(path + "/" + id + ".webtrec")),"UTF8"));
    
        // write in TREC format
        out.write("...");
    } catch (IOException e) {
        System.err.println("Error: " + e.getMessage());
        e.printStackTrace();
    } finally {
        try {
            out.close();
        } catch (IOException e) {
            System.err.println("Error: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
4

2 回答 2

3

是的。您正在泄漏文件描述符。

在第一种情况下,您打开一个数据库连接并且从不关闭它。连接通常会使用套接字或其他东西与数据库通信。由于您不关闭连接,因此不会关闭套接字,并且您将泄漏文件描述符。

在第二种情况下,我怀疑调用Jsoup.connect(urlVal)是打开一个连接,然后你没有关闭它。这将导致文件描述符泄漏。

更正 -界面上没有close()方法。Connection看起来必须创建实际连接,然后通过该get方法在内部关闭。假设是这样,在第二种情况下没有文件描述符泄漏。

第三种情况不会泄漏文件描述符。但是,如果您无法打开文件,out.close();语句将尝试在null... 上调用方法并抛出 NPE。


解决办法是找到你打开文件的所有地方、数据库连接、http连接,并确保句柄始终处于关闭状态。

一种方法是将close()调用(或等效的)放在一个块中...... finally但请确保您不会意外调用close().null

另一种方法是使用 Java 7 “try with resource”语法。例如:

private static void storeFile(String id, String url, String cat, 
                              String title, String contents) {
    try (BufferedWriter out = new BufferedWriter(
                new OutputStreamWriter(
                new FileOutputStream(
                new File(path + "/" + id + ".webtrec")),"UTF8"))) {
        // write in TREC format
        out.write("...");
        out.close();
    } catch (IOException e) {
        System.err.println("Error: " + e.getMessage());
        e.printStackTrace();
    }
}

(但请注意,Java 7 语法只能用于实现新Closeable接口的资源。)

于 2012-08-27T10:10:50.933 回答
2

补充斯蒂芬的综合分析。我建议为数据库使用连接池,尽管正如斯蒂芬指出的那样,除非您关闭这些连接,否则您将关闭池,但至少更容易发现原因......

我没有看到任何证据,但是您应该使用某种线程池来下载页面,这将有助于最大限度地利用系统资源。一些执行者服务就足够了。就像我说的那样,您可能已经这样做了,但是您没有显示任何代码(或评论)。

于 2012-08-27T10:15:55.910 回答