【问题标题】:Possible resource leak when reusing PreparedStatement?重用 PreparedStatement 时可能发生资源泄漏?
【发布时间】:2014-04-01 12:40:38
【问题描述】:

假设你有以下代码:

    Connection connection = null;
    PreparedStatement ps = null;

    try {
        Connection = connectionFactory.getConnection();

        ps = statement.prepareStamement(someQuery);
        // execute and read and stuff

        // now you want to use the ps again, since you don't want ps1, ps2, ps3, etc.
        ps = statement.prepareStatement(someOtherQuery); // DOES THIS FORM A POTENTIAL LEAK?
    } catch (a lot of exceptions) {
        // process exceptions
    } finally {

        // close the resources (using util class with null-checks and everything)
        SomeUtilClass.close(ps);
        SomeUtilClass.close(connection);
    }

重用 ps 变量是否存在潜在泄漏?

如果是这样,我不想声明多个此类准备好的语句(ps1、ps2、ps3 等)。我应该如何重构它?

有人想吗?

编辑

收到几个回答说这无关紧要。我想指出,我遇到的打开游标保持打开的时间有点过长,我想知道这种模式是否与它有关。

我的想法是:

第一个语句被取消引用并被 GC'ed,那么在这个例子中第一个 PreparedStatement 是如何关闭的(数据库方面)?

【问题讨论】:

    标签: java database jdbc resources


    【解决方案1】:

    重新使用准备好的语句变量 OF ITSELF 不会造成资源泄漏。这里的问题是,在将第一个语句重新分配给第二个语句之前,您没有关闭它。

    您可以将每个准备好的语句包装在其自己的 try/catch 块中,并在 finally 块中关闭,这样您就可以确保在继续下一个之前它已关闭。

    如果所需的逻辑是在第一个查询失败时中止以后的查询,则可以嵌套 try/catch 块,内部块关闭准备好的语句,然后重新抛出异常或抛出新异常。但在这一点上,坦率地说,我认为声明多个变量会更容易、更清晰。

    一般来说,重用变量并没有什么好处。您是否认为要节省内存?我不知道 PreparedStatement 对象需要多少内存,但我怀疑它超过了几十个字节。除非您有一个包含数千或数百万个数组的数组,否则不值得担心。如果您必须编写额外的代码来支持重用变量——比如额外的关闭语句——那么该代码可能会比额外的变量占用更多的内存。重用变量通常会使代码不太清晰,因为现在读者必须弄清楚,“哦,此时它包含第四个查询”或其他什么。

    (我曾经和一个不遗余力地重用变量的人一起工作,他会将它们重用于完全不同的事情。就像在函数的开头,某个字符串变量可能会持有客户数字,但在中途他会用它来保存邮政编码。当然,这使任何名称都变得毫无意义,所以他将所有的字符串变量称为 s1、s2、s3 等等;所有整数 i1、i2、i3、等等。试图阅读他的程序是一场噩梦。)

    【讨论】:

      【解决方案2】:

      这没有问题,只要正确关闭Connection即可。顺便说一句:在这种情况下,无需显式关闭(最后一个)语句,因为关闭 Connection 将为您执行此操作(并关闭通过它获取的所有其他资源)。

      更新 这取决于您对“有点太长”的定义。当您关闭连接时,通过它分配的所有资源(包括所有语句、通过这些语句创建的可能的结果集等)将尽快释放。根据 GC 何时启动,丢失的 PreparedStatements(包括它们的 ResultSets 等)可能会提前关闭,但没有人能保证这一点。因此,如果您确实需要依赖声明在超出范围时立即关闭,是的,那么您必须在创建新声明之前调用相应的关闭方法。

      【讨论】:

        【解决方案3】:

        通常是的,这可能形成内存泄漏(非托管资源)。不想说明显的,如果一个资源应该被关闭,那么你应该关闭它。在您的示例中,您丢失了对第一个准备好的语句的引用,因此无法关闭它们。

        泄漏引用的 GC 和终结器可能会执行必要的步骤,以从获取的任何非托管资源中重新校准内存,但是,最佳实践要求您应该确定性地执行此步骤。

        在您的示例中,为了关闭所有准备好的语句,您可以像这样使用 Iterable 集合:

        Deque<PreparedStatement> statements = new ArrayDeque<PreparedStatement>();
        try {
          statements.addFirst(statement.prepareStamement(someQuery));
          PreparedStatement statement = statements.getFirst();
          ..
        }...
        finally {
          // enumerate statements and close them.
        }
        

        【讨论】:

          【解决方案4】:

          这不一定会造成经典的“永久性”泄漏。垃圾收集器最终会到达准备好的语句的第一个实例,并调用它的终结器。

          但是,让垃圾收集器处理释放潜在的关键资源(例如 DB 句柄)并不是一个好习惯:您应该在重用准备好的语句变量之前调用 close 方法,或者根本不重用该变量.

          try {
              Connection = connectionFactory.getConnection();
          
              ps = statement.prepareStamement(someQuery);
              // execute and read and stuff
          
              // now you want to use the ps again, since you don't want ps1, ps2, ps3, etc.
              // v v v v v v v v v v v
              SomeUtilClass.close(ps);
              // ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
              ps = statement.prepareStatement(someOtherQuery); // DOES THIS FORM A POTENTIAL LEAK?
          } catch (a lot of exceptions) {
              // process exceptions
          } finally {
          
              // close the resources (using util class with null-checks and everything)
              SomeUtilClass.close(ps);
              SomeUtilClass.close(connection);
          }
          

          【讨论】:

            【解决方案5】:

            不,不是。这是因为 Java 运行时足够明智,可以在将 ps 引用的前一个对象中删除一个引用,然后再将其分配给其他对象。

            如果引用计数降至零,则该对象是垃圾回收的候选对象,随后将调用其终结器并释放资源。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2018-06-26
              • 1970-01-01
              • 1970-01-01
              • 2014-02-10
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多