为什么在方法返回之前,方法中声明的对象会进行垃圾回收?

时间:2010-03-20 09:47:28

标签: java garbage-collection

考虑在方法中声明的对象:

public void foo() {
    final Object obj = new Object();

    // A long run job that consumes tons of memory and 
    // triggers garbage collection
}

在 foo()返回之前,obj是否会受到垃圾收集的影响?

更新: 以前我认为obj不受垃圾收集的影响,直到 foo()返回。

然而,今天我发现自己错了。

我花了几个小时来修复bug,最后发现问题是由obj垃圾收集引起的!

任何人都能解释为什么会这样吗?如果我想要obj被固定如何实现它?

以下是有问题的代码。

public class Program
{
    public static void main(String[] args) throws Exception {
        String connectionString = "jdbc:mysql://<whatever>";

        // I find wrap is gc-ed somewhere
        SqlConnection wrap = new SqlConnection(connectionString); 

        Connection con = wrap.currentConnection();
        Statement stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, 
             ResultSet.CONCUR_READ_ONLY);
        stmt.setFetchSize(Integer.MIN_VALUE);

        ResultSet rs = stmt.executeQuery("select instance_id, doc_id from
               crawler_archive.documents");

        while (rs.next()) {
            int instanceID = rs.getInt(1);
            int docID = rs.getInt(2);

            if (docID % 1000 == 0) {
                System.out.println(docID);
            }
        }

        rs.close();
        //wrap.close();
    }
}

运行Java程序后,它会在崩溃之前打印以下消息:

161000
161000
********************************
Finalizer CALLED!!
********************************
********************************
Close CALLED!!
********************************
162000
Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: 

这是SqlConnection类的代码:

class SqlConnection
{
    private final String connectionString;
    private Connection connection;

    public SqlConnection(String connectionString) {
        this.connectionString = connectionString;
    }

    public synchronized Connection currentConnection() throws SQLException {
        if (this.connection == null || this.connection.isClosed()) {
            this.closeConnection();
            this.connection = DriverManager.getConnection(connectionString);
        }
        return this.connection;
    }

    protected void finalize() throws Throwable {
        try {
            System.out.println("********************************");
            System.out.println("Finalizer CALLED!!");
            System.out.println("********************************");
            this.close();
        } finally {
            super.finalize();
        }
    }

    public void close() {
        System.out.println("********************************");
        System.out.println("Close CALLED!!");
        System.out.println("********************************");
        this.closeConnection();
    }

    protected void closeConnection() {
        if (this.connection != null) {
            try {
                connection.close();
            } catch (Throwable e) {
            } finally {
                this.connection = null;
            }
        }
    }
}

7 个答案:

答案 0 :(得分:5)

我对此感到非常惊讶,但你是对的。它很容易重现,您不需要使用数据库连接等等:

public class GcTest {

    public static void main(String[] args) {
        System.out.println("Starting");

        Object dummy = new GcTest(); // gets GC'd before method exits

        // gets bigger and bigger until heap explodes
        Collection<String> collection = new ArrayList<String>();

        // method never exits normally because of while loop
        while (true) {
            collection.add(new String("test"));
        }
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("Finalizing instance of GcTest");
    }
}

运行:

Starting
Finalizing instance of GcTest
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:2760)
    at java.util.Arrays.copyOf(Arrays.java:2734)
    at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
    at java.util.ArrayList.add(ArrayList.java:351)
    at test.GcTest.main(GcTest.java:22)
像我说的那样,我几乎无法相信,但不可否认这些证据。

然而,它确实会产生一种反常的感觉,VM会发现该对象永远不会被使用,因此摆脱了它。这必须由规范允许。

回到问题的代码,你永远不应该依赖finalize()清理你的连接,你应该总是明确地做。

答案 1 :(得分:3)

由于您的代码已编写,“wrap”指向的对象不应该有资格进行垃圾回收,直到“wrap”在方法结束时从堆栈弹出。

正在收集它的事实告诉我你的代码编译后的与原始源不匹配,并且编译器已经做了一些优化,例如改变它:

SqlConnection wrap = new SqlConnection(connectionString); 
Connection con = wrap.currentConnection();

到此:

Connection con = new SqlConnection(connectionString).currentConnection();

(或者甚至内联整个事情)因为在这一点之外没有使用“换行”。创建的匿名对象将立即符合GC的条件。

唯一可以确定的方法是反编译代码并查看已对其执行的操作。

答案 2 :(得分:2)

这里真的发生了两件不同的事情。 obj是一个堆栈变量,设置为对Object的引用,Object在堆上分配。堆栈将被清除(通过堆栈指针算法)。

但是,Object本身将被垃圾收集清除。所有堆分配的对象都受GC影响。

编辑:为了回答您更具体的问题,Java规范不保证在任何特定时间 收集(参见the JVM spec)(当然它将在之后收集 / em>最后一次使用)。所以它只能回答特定的实现。

编辑:澄清,根据评论

答案 3 :(得分:1)

我确信你知道,Java垃圾收集和终结是非确定性的。在这种情况下,您可以确定的是wrap 符合的垃圾收集时间。我假设您在方法返回时(wrap超出范围)询问wrap是否仅符合GC条件。我认为一些JVM(例如带有-server的HotSpot)不会等待从堆栈中弹出对象引用,只要没有其他引用它就会使它符合GC的条件。看起来这就是你所看到的。

总而言之,在方法退出之前,您依赖的终结速度足够慢,无法最终确定SqlConnection的实例。您终结器正在关闭SqlConnection不再负责的资源。相反,您应该让Connection对象负责自己的最终确定。

答案 4 :(得分:1)

  

在foo()返回之前,obj是否会受到垃圾回收?

obj返回之前,您无法确定会收集foo,但在foo返回之前,它肯定有资格收集。

  

有人可以解释为什么会这样吗?

GC收集无法到达的对象。在foo返回之前,您的对象可能无法访问。

范围无关紧要。在obj返回之前foo保持在堆栈上的想法是一种过于简单化的心理模型。真实的系统不能那样工作。

答案 5 :(得分:0)

这里,obj是方法中的局部变量,只要方法返回或退出,它就会从堆栈中弹出。这使得无法到达堆上的Object对象,因此它将被垃圾收集。只有在引用Object从堆栈中弹出后,即只有在方法完成或返回后,堆上的obj对象才会被GC。


修改 要回答您的更新,

UPDATE: Let me make the question more clear. 
Will obj be subject to garbage collection before foo() returns?

obj只是实际对象引用。这里{f}在方法foo()中声明obj。所以你的问题Will obj be subject to garbage collection before foo() returns?不适用,因为obj在方法foo()运行时进入堆栈框架内,并在方法完成时消失。

答案 6 :(得分:0)

根据当前的规范,从最终确定到正常使用,甚至没有发生在之前。因此,为了强加顺序,你实际上需要使用一个锁,一个volatile,或者,如果你是绝望的,可以存储一个可以从静态访问的引用。范围当然没有什么特别之处。

你真的需要写一个终结者应该很少见。