【问题标题】:Java memory leak, destroy/finalize objectJava 内存泄漏,销毁/终结对象
【发布时间】:2011-04-17 17:43:01
【问题描述】:

我遇到了内存泄漏,代码类似于下面的代码(这是一个在每个循环中都有不同输入的模拟)。

问题

对象 Object_XXX 相当复杂,它与数据库的连接以及其他填充了数据库数据的对象。

    for(int i=0; i<MAX; i=i+1){
        Class_XXX Object_XXX = new Class_XXX(Arg_1, Arg_2);

        // action with Object_XXX
    }

现在,在调用了几个方法之后,Object_XXX 也可以被丢弃,因为下一个循环将需要一个具有不同特征的对象(不同的数组、数组的大小、嵌套对象...) .

构造函数与下面类似,其他类也有类似构造函数。

public Class_XXX(Arg_1, Arg_2, DB_Connection){

    try {
        Statement Query_Statement = null;
        ResultSet Query_ResultSet = null;
        String Query_String = null;

        Query_String = "...";
        Query_Statement = DB_Connection.createStatement();
        Query_ResultSet = Query_Statement.executeQuery(SQL);

        while (Query_ResultSet .next()) {
            this.Param_1 = Query_ResultSet .getString("param_1");
    this.Param_2 = Query_ResultSet .getString("param_2");
            ...
    this.Param_n = Query_ResultSet .getString("param_n");
        }
    } catch (SQLException e) {
        System.out.println("SQL Exception: "+ e.toString());
    }
}

问题

在这种情况下,最正确的方法是什么? a) 在循环结束时完成 Object_XXX b) 在代码中不使用组成 Object_XXX 的每个对象时,最终确定它们吗? 就我个人而言,我更喜欢 a) 因为 我认为 这会让垃圾收集器继续工作而不会造成太多麻烦

能否提供代码示例或参考资料?

谢谢!


第二轮:

在找到下面的答案并查看另一个页面 (http://accu.org/index.php/journals/236) 之后,这是我现在用于构造函数的模板。太早了,看它是否有效。仍然存在“exception.toString”,但真正的代码会在异常情况下为变量提供标准值,并在日志中报告操作。

public Class_XXX(String Object_Name, java.sql.Connection Query_Connection){

    try{ // begin try-catch for Query_Connection
        Statement Query_Statement = Query_Connection.createStatement();
        try { // begin try-finally for Query_Statement
            String Query_String = "SELECT param_1, param_2, ... param_3 FROM table_name WHERE object_name = '" + Object_Name + "'";
            ResultSet Query_ResultSet = Query_Statement.executeQuery(Query_String);
            try { // begin try-finally for Query_ResultSet

                while (Query_ResultSet.next()) {
                    this.Param_1 = Query_ResultSet.getString("param_1");
                    this.Param_2 = Query_ResultSet.getString("param_2");
                    // ...
                    this.Param_n = Query_ResultSet.getString("param_n");
                }

            } finally {
                try { Query_ResultSet.close(); }
                catch (SQLException ex) { System.out.println("Error in Class_XXX constructor - " + ex.toString()); }
            } // end try-finally for Query_ResultSet

        } finally {
            try { Query_Statement.close(); }
            catch (SQLException ex) { System.out.println("Error in Class_XXX constructor - " + ex.toString()); }
        } // end try finally for Query_Statement

    } catch(SQLException ex) {
            System.out.println("Error in Class_XXX constructor - " + ex.toString());
    } // end try-catch for Query_Connection

}

【问题讨论】:

  • 这取决于您使用的 SQL API...如果结果集有某种关闭方法,您绝对应该在某个时候调用它。
  • (您可能想尝试 Execute Around 习语来摆脱一些通用代码(尽管直到[/if] 我们得到 lamdas/简洁的匿名内部类,它仍然很冗长)。)跨度>

标签: java constructor garbage-collection destructor


【解决方案1】:

如果某些东西仍然持有对它的引用,则终结对象不会从内存中删除它。如果没有任何东西持有该引用,垃圾收集器无论如何都会将其删除,因此最终确定在这里没有多大意义。

如果您遇到内存泄漏,某些东西必须保持对您的对象的引用,因此它们不能被垃圾收集。我建议使用一些分析器来看看它是什么。

【讨论】:

  • +1。查看示例代码,我很确定他们没有关闭大部分对象,例如 ResultSets,因此有多个泄漏。强制 GC 或将对象设置为 null 在这里无济于事,还有太多其他隐藏的东西,比如 JDBC 和奇怪的 DB_Connection 类。
  • Ja,profiler显示问题其实出在JDBC上。
【解决方案2】:

看起来你是一个习惯于做 RAII 的 C++ 程序员。

Java 不支持具有与 C++ 等效语义的 RAII,因为对象销毁由垃圾收集器完成,有时在对象变得无法访问之后,在单独的后台线程中。因此,几乎没有人使用 finalize 方法(否则它相当于 C++ 析构函数)来释放资源。

对于只占用内存的对象,Java 不需要 RAII,因为它们的内存会在它们变得无法访问后的某个时间被垃圾收集器自动回收。特别是,不需要显式释放成员。

管理内存以外的资源(例如文件描述符)或希望立即执行清理工作的对象通常提供清理方法,必须显式调用该方法。从他们的 javadoc 中可以看出,StatementResultSet 的实例就是这样的对象(它们指的是 vm 之外的资源,希望及时释放)。以异常安全的方式调用清理方法的典型模式是:

Statement statement = connection.createStatement();
try {
    ResultSet resultset = statement.executeQuery(sql);
    try {
        // read the resultset
    } finally {
        resultset.close();
    }
} finally {
    statement.close();
}

还有一些风格问题:

  • exception.toString() 仅包含异常消息。 exception.printStackTrace() 额外打印整个堆栈跟踪。
  • 几乎每个 java 程序员都遵循包名以小写字母开头、类名以大写字母开头、字段/变量以小写字母开头的约定。此外,单词通常使用驼峰式而不是_分隔。

【讨论】:

  • 谢谢!对于示例和说明。当我在我的 1000 多个课程中实现它时,我们会看看它是否有效。以前从未听说过这个 CamelCase,但在 Wikipedia 上快速搜索一下就直接指出了这一点:en.wikipedia.org/wiki/…我会尽快加入主流!
【解决方案3】:

不推荐使用终结方法。 你最好把它留给垃圾收集器。 但是,您应该释放嵌套资源(关闭它们,分配 null)。

【讨论】:

  • 将 null 分配给拥有有限资源的变量在大多数情况下会导致程序快速降级(因为它可能永远不会被 GC)。基本上,这就是这里发生的事情,因为一旦他的构造函数结束,所有局部变量都可用于 GC。
  • @Alexander Pogrebnyak 你是绝对正确的。我的意思是应该明确释放指向某些外部资源的对象。对象数组也是如此。为独立对象分配 NULL 是可以的。
【解决方案4】:

男孩,多好的构造函数。

你分配了很多局部变量,并没有向我们展示任何释放它们的代码,例如

    Statement Query_Statement = null;
    ResultSet Query_ResultSet = null;

尽管在任何情况下您都不应该自己调用 finalize,但无论如何在这里调用它对您没有帮助,因为它无法访问您的构造函数中声明的局部变量。

学习遵循这种模式:

final Statement stmt = createStatement( );

try
{
  useStatement( stmt );
}
finally
{
  stmt.close( );
}

这是防止资源(不仅仅是内存)泄漏的唯一方法。

另外,以你的方式吞下异常是一个非常糟糕的主意。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-12-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-18
    • 2014-05-16
    • 1970-01-01
    • 2012-09-12
    • 1970-01-01
    相关资源
    最近更新 更多