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

SiL*_*oNG 5 java garbage-collection

考虑一个方法中声明的对象:

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

    // A long run job that consumes tons of memory and 
    // triggers garbage collection
}
Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

161000
161000
********************************
Finalizer CALLED!!
********************************
********************************
Close CALLED!!
********************************
162000
Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: 
Run Code Online (Sandbox Code Playgroud)

这是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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

ska*_*man 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");
    }
}
Run Code Online (Sandbox Code Playgroud)

运行:

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)
Run Code Online (Sandbox Code Playgroud)

就像我说的,我简直不敢相信,但不可否认证据.

尽管如此,它确实会产生一种反常的感觉,VM会发现该对象从未被使用过,因此将其删除.这必须由规范允许.

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


Pao*_*olo 3

在编写代码时,“wrap”指向的对象不符合垃圾回收条件,直到“wrap”在方法末尾从堆栈中弹出。

它被收集的事实表明,您编译的代码与原始源代码不匹配,并且编译器已经做了一些优化,例如更改此内容:

SqlConnection wrap = new SqlConnection(connectionString); 
Connection con = wrap.currentConnection();
Run Code Online (Sandbox Code Playgroud)

对此:

Connection con = new SqlConnection(connectionString).currentConnection();
Run Code Online (Sandbox Code Playgroud)

(或者甚至内联整个内容)因为超出这一点就不再使用“wrap”。创建的匿名对象将立即有资格进行 GC。

唯一确定的方法是反编译代码并查看对其做了什么。