考虑在方法中声明的对象:
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;
}
}
}
}
答案 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,或者,如果你是绝望的,可以存储一个可以从静态访问的引用。范围当然没有什么特别之处。
你真的需要写一个终结者应该很少见。