如果Java finalize 方法中存在无限循环或死锁,Finalizer 线程会做什么。
3 回答
规范写道:
在垃圾收集器回收对象的存储空间之前,Java 虚拟机将调用该对象的终结器。
Java 编程语言没有指定终结器将在多长时间内被调用,只是说它会在对象的存储被重用之前发生。
我读到这意味着终结器必须在存储可以重用之前完成。
Java 编程语言没有指定哪个线程将为任何给定对象调用终结器。
重要的是要注意许多终结器线程可能处于活动状态(有时在大型共享内存多处理器上需要这样做),并且如果大型连接数据结构变成垃圾,则可以调用该数据结构中每个对象的所有终结方法同时,每个终结器调用都在不同的线程中运行。
也就是说,终结可能发生在垃圾收集器线程中,也可能发生在单独的线程中,甚至可能发生在单独的线程池中。
JVM 不允许简单地中止执行终结器,并且只能使用有限数量的线程(线程是操作系统资源,操作系统不支持任意数量的线程)。因此,非终止终结器必然会饿死该线程池,从而禁止收集任何可终结对象,并导致内存泄漏。
以下测试程序证实了这种行为:
public class Test {
byte[] memoryHog = new byte[1024 * 1024];
@Override
protected void finalize() throws Throwable {
System.out.println("Finalizing " + this + " in thread " + Thread.currentThread());
for (;;);
}
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Test();
}
}
}
在 Oracle JDK 7 上,这会打印:
Finalizing tools.Test@1f1fba0 in thread Thread[Finalizer,8,system] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at tools.Test.<init>(Test.java:5) at tools.Test.main(Test.java:15)
我想说,由于 Java 规范没有说明必须如何调用 finalize 方法(只是必须在对象被垃圾收集之前调用它),因此行为是特定于实现的。
该规范不排除有多个线程运行该进程,但不需要它:
重要的是要注意许多终结器线程可能处于活动状态(有时在大型共享内存多处理器上需要这样做),并且如果大型连接数据结构变成垃圾,则可以调用该数据结构中每个对象的所有终结方法同时,每个终结器调用都在不同的线程中运行。
查看JDK7的源代码,FinalizerThread
保持对象队列计划完成(实际上对象被GC添加到队列中,当证明无法访问时 - 检查ReferenceQueue
文档):
private static class FinalizerThread extends Thread {
private volatile boolean running;
FinalizerThread(ThreadGroup g) {
super(g, "Finalizer");
}
public void run() {
if (running)
return;
running = true;
for (;;) {
try {
Finalizer f = (Finalizer)queue.remove();
f.runFinalizer();
} catch (InterruptedException x) {
continue;
}
}
}
}
每个对象都从队列中移除,并runFinalizer
在其上运行方法。检查是否已经在对象上运行了终结,如果没有,它正在被调用,作为对本机方法的调用invokeFinalizeMethod
。该方法只是调用finalize
对象上的方法:
JNIEXPORT void JNICALL
Java_java_lang_ref_Finalizer_invokeFinalizeMethod(JNIEnv *env, jclass clazz,
jobject ob)
{
jclass cls;
jmethodID mid;
cls = (*env)->GetObjectClass(env, ob);
if (cls == NULL) return;
mid = (*env)->GetMethodID(env, cls, "finalize", "()V");
if (mid == NULL) return;
(*env)->CallVoidMethod(env, ob, mid);
}
这应该会导致这样一种情况,即对象在列表中排队,而在FinalizerThread
有故障的对象上被阻塞,这反过来又会导致OutOfMemoryError
.
所以回答原来的问题:
如果 Java finalize 方法中存在无限循环或死锁,Finalizer 线程会做什么。
它会简单地坐在那里并运行那个无限循环,直到OutOfMemoryError
.
public class FinalizeLoop {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
for (;;) {
new FinalizeLoop();
}
}
};
thread.setDaemon(true);
thread.start();
while (true);
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("Finalize called");
while (true);
}
}
如果在 JDK6 和 JDK7 上只打印一次,请注意“Finalize called”。
对象不会被“释放”,即不会从它们那里收回内存,并且在 finalize 方法中释放的资源将始终保留。
基本上有一个队列持有所有等待它们的 finalize() 方法被执行的对象。终结器线程从此队列中拾取对象 - 运行终结器 - 并释放对象。
如果该线程将被死锁,则 ReferenceQueue 队列将增长,并且在某些时候 OOM 错误将变得不可避免。此外,资源将被此队列中的对象占用。希望这可以帮助!!
for(;;)
{
Finalizer f = java.lang.ref.Finalizer.ReferenceQueue.remove();
f.get().finalize();
}