不关闭我的 JDBC PreparedStatements 是不是会导致内存泄漏?

Posted

技术标签:

【中文标题】不关闭我的 JDBC PreparedStatements 是不是会导致内存泄漏?【英文标题】:Do I get a memory leak by not closing my JDBC PreparedStatements?不关闭我的 JDBC PreparedStatements 是否会导致内存泄漏? 【发布时间】:2011-09-19 19:56:01 【问题描述】:

我正在使用 java.sql PreparedStatements,我想知道以下问题:

在Java is Pass-by-Value, Dammit! 中,给出了以下作为 Java 的 Pass-By 约定的示例:

public void foo(Dog d) 
    d = new Dog("Fifi"); // creating the "Fifi" dog


Dog aDog = new Dog("Max"); // creating the "Max" dog
// at this point, aDog points to the "Max" dog
foo(aDog);
// aDog still points to the "Max" dog 

在我的代码中,这出现如下(半 Java 伪代码):

public void method() 
  PreparedStatement pstmt = null;
  ResultSet rs = null;
  try 
    rs = executeStatement(sql-string, pstmt, conn, vars...);
   catch (....)  /* error-handling */ 
  /// do stuff with the data
  rs.close();

executeStatement 是(类似于)以下内容:

ResultSet executeStatement(String sql, PreparedStatement pstmt, Connection conn, Object[] vars...) 
  pstmt = conn.prepareStatement(sql);
  /// set pstmt variables...
  ResultSet rs = pstmt.execute();
  return rs;

根据我对 Java 的传递约定的理解,我在主代码中使用 pstmt 做任何事情都没有用,因为即使在调用 executeStatement 之后它仍然是 null。但是,因为关闭 PreparedStatement 也会关闭 ResultSet,所以我知道在处理 ResultSet 时在 executeStatement 中创建的 PreparedStatement 不会关闭。

这是否意味着这里存在内存泄漏? (我对内存泄漏以及如何诊断/修复它们的理解充其量是参差不齐的)。有什么方法可以让我以不同的方式构造它以避免泄漏,但继续使用可以执行 SQL 字符串并以抽象方式返回 ResultSet 的方法?

【问题讨论】:

在上面的代码中, psmt 以 null 开始,但如果 conn.prepareStatemenet(...) 返回不为 null 的内容,则在方法完成后,全局变量 psmt 将不为 null。但是,在连接对象上调用 close(),与创建 PreparedStatement 和 ResultSet 相同的对象也会关闭这些对象,所以你应该没问题,没有 JDBC 资源仍然是垃圾不可回收... 哦,太好了!我忘记了关闭连接会释放这些资源。谢谢! 只是为了回答按值传递/按引用传递的问题:是的,您的 executeStatement 方法的 pstmt 参数在当前形式中根本没有任何功能,因为它的 @987654332 @值在方法的第一个赋值中被覆盖。 【参考方案1】:

首先,传统意义上的内存泄漏,即分配的数据不能再被引用,在 Java 中不存在,因为只有没有被引用的数据才会被收集。但是,在这种情况下,您无论如何都不是在寻找内存泄漏,而是在寻找资源泄漏(这在 Java 中是一个更大的问题):PreparedStatement 的内存最终将被收集并释放内存,因为它是在执行您的方法后不再引用,但是,语句持有的资源应该更早地释放,而不仅仅是在垃圾收集器运行时释放。

您可以做的是编写一个包含StatementResultSet 作为成员的类并返回这个类,如下所示:

class ResultSetStatementPair 
  ResultSetStatementPair(ResultSet rs, Statement stmt) 
    this.rs = rs; this.stmt = stmt;
  

  ResultSet rs;
  Statement stmt;


ResultSetStatementPair executeStatement(String sql, Connection conn, Object[] vars...) 
  PreparedStatement pstmt = conn.prepareStatement(sql);
  ResultSet rs = pstmt.execute();
  return new ResultSetStatementPair(rs, pstmt);


public void method() 
  Statement pstmt = null;
  ResultSet rs = null;
  try 
    ResultSetStatementPair pair = executeStatement(sql-string, pstmt, conn, vars...);
    rs = pair.rs;
    stmt = pair.stmt;
    // do stuff with the data
   catch (....)  /* error-handling */ 
  finally  
    if(rs != null) rs.close();
    if(stmt != null) stmt.close();
  

还注意到我添加了 finally 并将 do stuff 移动到 try 块中。

【讨论】:

【参考方案2】:

我在这里没有看到内存泄漏。我发现的唯一问题是您没有在 finally 块中关闭结果集。因此,如果抛出异常,则 rs.close() 将不会被执行。

正如 Andrei 所说,关闭结果集也将关闭基础语句。我不确定你在哪里关闭连接,但这也应该发生在 finally 块中。

【讨论】:

是的,你应该这样做。感谢上帝,Java 7 对资源的尝试将摆脱这种样板代码。 @Andrei Bodnarescu - 我不知道。我还没有涉足 Java 7。 哦,嘿,如果您使用的是 Java 7,请务必尝试使用资源,这很酷。这里有一个例子:java7developer.com/blog/?p=24(页面上的最后一个代码sn-p) @BenCole - 你不应该怀疑在 finally 块中关闭。这是关闭资源的标准和推荐方式。 @KoheiNozaki 我认为这是相反的,关闭语句应该关闭结果集。但是,在某些数据库服务器(例如 Oracle)的情况下,不显式关闭结果集最终会耗尽服务器端的游标数量。最终,语句和结果集都将被垃圾收集。好的做法是在 finally 块中分别关闭它们,然后将它们设置为 null。

以上是关于不关闭我的 JDBC PreparedStatements 是不是会导致内存泄漏?的主要内容,如果未能解决你的问题,请参考以下文章

不关闭我的 JDBC PreparedStatements 是不是会导致内存泄漏?

JDBC 删除方法似乎在我的 Spring Boot 服务中不起作用[关闭]

jdbc:我啥时候可以关闭啥

如何将 JDBC4PreparedStatement 与期望 OUT 参数的 MySQL 存储过程一起使用?

JDBC连接池[关闭]

Java - JDBC替代品[关闭]