Why an object declared in method is subject to gar

2019-04-09 00:48发布

Consider an object declared in a method:

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

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

Will obj be subject to garbage collection before foo() returns?

UPDATE: Previously I thought obj is not subject to garbage collection until foo() returns.

However, today I find myself wrong.

I have spend several hours in fixing a bug and finally found the problem is caused by obj garbage collected!

Can anyone explain why this happens? And if I want obj to be pinned how to achieve it?

Here is the code that has problem.

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();
    }
}

After running the Java program, it will print the following message before it crashes:

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

And here is the code of class 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条回答
一纸荒年 Trace。
2楼-- · 2019-04-09 01:10

Here, obj is a local variable in the method and it is popped off the stack as soon as the method returns or exits. This leaves no way to reach the Object object on the heap and hence it will be garbage collected. And the Object object on the heap will be GC'd only after its reference obj is popped off the stack,ie, only after the method finishes or returns.


EDIT: To answer your update,

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

obj is just a reference to the actual object on the heap. Here obj is declared inside the method foo(). So your question Will obj be subject to garbage collection before foo() returns? doesnot apply as obj goes inside the stack frame when the method foo() is running and is gone when the method finishes.

查看更多
兄弟一词,经得起流年.
3楼-- · 2019-04-09 01:20

There are really two different things happening here. obj is a stack variable being set to a reference to the Object, and the Object is allocated on the heap. The stack will just be cleared (by stack pointer arithmetic).

But yes, the Object itself will be cleared by garbage collection. All heap-allocated objects are subject to GC.

EDIT: To answer your more specific question, the Java spec does not guarantee collection by any particular time (see the JVM spec) (of course it will be collected after its last use). So it's only possible to answer for specific implementations.

EDIT: Clarification, per comments

查看更多
Fickle 薄情
4楼-- · 2019-04-09 01:20

As I'm sure you're aware, in Java Garbage Collection and Finialization are non-deterministic. All you can determine in this case is when wrap is eligible for garbage collection. I'm assuming you are asking if wrap only becomes eligible for GC when the method returns (and wrap goes out of scope). I think that some JVMs (e.g. HotSpot with -server) won't wait for the object reference to be popped from the stack, it will make it eligible for GC as soon as nothing else references it. It looks like this is what you are seeing.

To summarise, you are relying on finalization being slow enough to not finalize the instance of SqlConnection before the method exits. You finalizer is closing a resource that the SqlConnection is no longer responsible for. Instead, you should let the Connection object be responsible for its own finalization.

查看更多
Deceive 欺骗
5楼-- · 2019-04-09 01:22

As your code is written the object pointed to by "wrap" shouldn't be eligible for garbage collection until "wrap" pops off the stack at the end of the method.

The fact that it is being collected suggests to me your code as compiled doesn't match the original source and that the compiler has done some optimisation such as changing this:

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

to this:

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

(Or even inlining the whole thing) because "wrap" isn't used beyond this point. The anonymous object created would be eligible for GC immediately.

The only way to be sure is to decompile the code and see what's been done to it.

查看更多
在下西门庆
6楼-- · 2019-04-09 01:24

According to the current spec, there isn't even a happens-before ordering from finalisation to normal use. So, to impose order, you actually need to use a lock, a volatile or, if you are desperate, stashing a reference reachable from a static. There is certainly nothing special about scope.

It should be rare that you actually need to write a finaliser.

查看更多
Deceive 欺骗
7楼-- · 2019-04-09 01:28

Will obj be subject to garbage collection before foo() returns?

You cannot be sure obj will be collected before foo returns but it is certainly eligible for collection before foo returns.

Can anyone explain why this happens?

GCs collect unreachable objects. Your object is likely to become unreachable before foo returns.

Scope is irrelevant. The idea that obj stays on the stack until foo returns is an overly-simplistic mental model. Real systems don't work like that.

查看更多
登录 后发表回答