如果没有明确关闭 ResultSet 会泄漏吗?

Posted

技术标签:

【中文标题】如果没有明确关闭 ResultSet 会泄漏吗?【英文标题】:Will ResultSet leak if not explicitly closed? 【发布时间】:2014-04-03 21:01:05 【问题描述】:

我正在使用 jdbc 连接池和码头,在服务器实例上进行了一年多没有问题的设置。我已经切换到一个新的 ubuntu 服务器,并且内存不断不足。我正在分析内存使用情况并查看以下***实例:

java.util.Hashtable$Entry (33%)
com.mysql.jdbc.ConnectionPropertiesImpl$BooleanConnectionProperty (13%)
com.mysql.jdbc.ConnectionPropertiesImpl$StringConnectionProperty (3%)
com.mysql.jdbc.ConnectionPropertiesImpl$IntegerConnectionProperty (3%)

我没有明确关闭我的 ResultSet 实例,我正在做类似的事情:

Connection conn = null;
PreparedStatement stmt = null;
try 
    conn = ...;
    stmt = ...;
    ResultSet rs = ...;
    rs.useIt();

finally 
    if (stmt != null)  stmt.close(); 
    if (conn != null)  conn.close(); 

文档说 Statement 将在关闭时关闭关联的 ResultSet 实例 - 这台 ubuntu 机器上的 jdbc 实现是否可能实际上没有这样做?我没有对我的代码(一个打包的 .war 文件)进行任何更改,我只是将它按原样放到了这个 ubuntu 机器上的一个码头实例中。

分析显示那些 com.mysql.jdbc.ConnectionPropertiesImpl 实例继续增长,所以猜测也许这就是正在发生的事情。

我只是想在出发前检查一下并修改我的所有代码以显式关闭 ResultSet 实例。查看 MySql Workbench,我在 Client Connections 视图中看不到任何异常 - 我的应用程序连接进来并且似乎得到了清理。

谢谢

-------- 更新--------

我已更改所有 ResultSet 实例以立即关闭它们以及在 finally 块中,就像我的 Connection 和 PreparedStatement 实例一样。但这似乎没有帮助,内存一直在增长。

又看了看,我注意到了这个类:

com.mysql.jdbc.JDBC4Connection

并且实例的数量永远不会减少。大约 10 分钟的正常运行时间后,我看到了将近 5k 个实例(哎呀?)。

这篇文章看起来与我遇到的非常相似:

Memory leak in JDBC4Connection

OP最后说:

ORM 依赖于关闭连接以释放终结器中的资源(有时会关闭结果集和语句),但池将连接保持打开几个小时,如果出现任何高峰,这会导致 OOM。

我不明白这意味着什么,也不明白如何解决这个问题。我很确定我现在正在到处关闭我的资源(否则当我在我的其他 Windows 服务器上运行相同的 web 应用程序时,我可能也看到了那里的泄漏?)。

-------- 更新--------

仍然看到相同的行为,这就是我在处理的每个请求中打开数据库连接的方式(省略了错误检查):

 public class DsHelper 
    private static DsHelper sInstance;
    private DataSource mDs;

    public DsHelper() 
        InitialContext ctx = new InitialContext();
        mDs = (DataSource)ctx.lookup("java:comp/env/jdbc/myds");
    

    public static DsHelper get()  
        if (sInstance == null) 
            sInstance = new DsHelper();
        

        return mInstance;
    

    public DataSource getDataSource() 
        return mDs;
    

使用它:

protected void doGet(HttpServletRequest request, 
                     HttpServletResponse response) 

    Connection conn = null; 
    try 
        conn = DsHelper.get().getDataSource();
        ...
    
    finally 
        if (conn != null)  conn.close(); 
    

【问题讨论】:

关闭ResultSet 也是一个好习惯,但我认为关闭Statement 也会使ResultSet 失效(我怀疑关闭)。我不相信它是有保证的,这就是为什么你应该clean up after yourself。 我认为它们都已关闭,但内存仍在增长 - 不过用一些新信息更新了我的 q,谢谢! 你是如何打开连接的?您使用 DriverManager 还是某种 DataSource(哪个?)? @MarkRotteveel 用我打开连接的方式更新了答案。 @user3203425 说真的,看看我上面链接的博客文章。由于 WebLogic 的 Oracle 问题,我写了这个,但它在那里工作。 【参考方案1】:

ResultSet 泄漏是众所周知的普遍现象。明确地关闭它们是一个非常好的主意,值得你努力去做的每一点麻烦。去这样做!认真的。

不同的 VM 实现处理垃圾回收的方式不同。这可能就是您在新环境中看到东西堆积如山的原因。

在 Oracle 上,ResultSet 对象是稀缺服务器资源(游标)的反映,如果您不明确关闭它们,最终您的服务器会耗尽,其他连接的工作开始失败。

去清理它们!

【讨论】:

好的,我认为它们现在都已明确关闭,但仍然看到相同的行为。用一些更新的信息更新了我的原始帖子,谢谢!【参考方案2】:

基于 Statement javadoc,ResultSet 不需要显式关闭。

From javadocs page,

注意:当一个Statement对象关闭时,它当前的ResultSet对象, 如果存在,也关闭。

但我建议在任务完成后立即关闭 ResultSet。 Oracle 的 Statement 实现即使在关闭连接后也存在打开游标的问题。因此,有可能出现“超出最大打开游标”等异常。

您也可以使用 java 1.7 中引入的最新功能来避免显式关闭 Statement。 更好更简洁的代码会是(如果您使用的是 Java 1.7+)

public static void viewTable(Connection con) throws SQLException 

    String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";

    try (Statement stmt = con.createStatement()) 
        ResultSet rs = stmt.executeQuery(query);

        while (rs.next()) 
            String coffeeName = rs.getString("COF_NAME");
            int supplierID = rs.getInt("SUP_ID");
            float price = rs.getFloat("PRICE");
            int sales = rs.getInt("SALES");
            int total = rs.getInt("TOTAL");

            System.out.println(coffeeName + ", " + supplierID + ", " + 
                               price + ", " + sales + ", " + total);
        
     catch (SQLException e) 
        JDBCTutorialUtilities.printSQLException(e);
    

观察 try 块语法的差异并观察没有显式的 close 方法调用。它在内部得到照顾。参考——try-with-resources Statement

【讨论】:

正确关闭资源取决于具体的 JDBC 驱动实现。 API 记录它的事实并不一定意味着所有实现都符合! @MarkRotteveel 但他们肯定会这样做吗?他们肯定要通过一个认证步骤吗? @EJP 否。另请参阅 DB2 的批处理实现。提示,它不像其他人的。 @EJP 有 - afaik - 没有真正的认证,除了通过 TCK;作为一个开源项目,你需要大量的繁文缛节和跳过甲骨文的官僚机构……而且作为一家商业企业,每年都会向甲骨文分钱。

以上是关于如果没有明确关闭 ResultSet 会泄漏吗?的主要内容,如果未能解决你的问题,请参考以下文章

.什么是JDBC的最佳实践?

什么是数据库连接泄漏

Flutter - 关闭小部件时 BLoC 流实例会导致内存泄漏吗?

不关闭 stringwriter 会导致泄漏吗?

不关闭 stringwriter 会导致泄漏吗?

如何正确使用带有 h:dataTable 的 ResultSet