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;
}
}
}
}
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 theObject
object on the heap and hence it will be garbage collected. And theObject
object on the heap will be GC'd only after its referenceobj
is popped off the stack,ie, only after the method finishes or returns.EDIT: To answer your update,
obj
is just a reference to the actual object on the heap. Hereobj
is declared inside the method foo(). So your questionWill obj be subject to garbage collection before foo() returns?
doesnot apply asobj
goes inside the stack frame when the methodfoo()
is running and is gone when the method finishes.There are really two different things happening here.
obj
is a stack variable being set to a reference to theObject
, and theObject
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
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 ifwrap
only becomes eligible for GC when the method returns (andwrap
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 theSqlConnection
is no longer responsible for. Instead, you should let theConnection
object be responsible for its own finalization.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:
to this:
(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.
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.
You cannot be sure
obj
will be collected beforefoo
returns but it is certainly eligible for collection beforefoo
returns.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 untilfoo
returns is an overly-simplistic mental model. Real systems don't work like that.