我有PR,PR指向的对象O,以及为PR设置的RQ。我有一个继续轮询RQ的线程,在RQ中找到的第一个引用,线程打印它找到它的时间,然后退出。
事情很好,但是当O有一个finalize(无论多么微不足道)时,线程不再在RQ中找到引用并且无限期地继续运行。
问题:为什么会这样?我正在使用Sun JDK 1.6。
以下是代码:
good case
public class MyGCPhantom
{
public static void main(String[] args) throws InterruptedException
{
GCPhantomObject p = new GCPhantomObject();
ReferenceQueue phantomQueue = new ReferenceQueue();
PhantomReference<GCPhantomObject> pr = new PhantomReference<GCPhantomObject>(p, phantomQueue);
new GCPhantomThread(phantomQueue, "Phantom").start();
p = null;
System.gc();
}
}
class GCPhantomObject
{
@Override
protected void finalize()
{
//System.out.println("GCPhantom finalized " + System.currentTimeMillis());
}
}
class GCPhantomThread extends Thread
{
private ReferenceQueue referenceQueue;
private String name;
GCPhantomThread(ReferenceQueue referenceQueue, String name)
{
this.referenceQueue = referenceQueue;
this.name = name;
}
@Override
public void run()
{
while(referenceQueue.poll() == null);
System.out.println(name + " found at " + System.currentTimeMillis());
}
}
bad case
只需在finalize()
的{{1}}中取消注释SOP。
答案 0 :(得分:4)
您的分析有些偏差。在两者好的情况和坏的情况下,您的对象实现finalize
。在好的情况下,它实现了它;在坏情况下,非平凡。因此,明显的问题在于finalize
的简单和非平凡实现之间的区别。
我认为没有理由为什么JVM会被规范强制排队你的引用。你做一次GC运行,然后继续等待发生的事情。众所周知,任何非平凡的终结器都可以复活对象,因此在入队之前可能需要更多的GC周期。我建议添加更多GC电话。
另请注意,我们不建议您决定使用poll
而不是remove
。您应该使用阻止调用来阻止忙碌轮询。
供参考,这些是文档中的相关定义:
如果垃圾收集器在某个时间点确定幻像引用的引用是幻像可达的,那么在那个时间或稍后它会将引用排入队列。
如果一个物体既没有强烈,柔和,也没有微弱的可触及,它已被最终确定,并且某些幻像参考指的是它。
已完成的对象已自动调用其终结器。
答案 1 :(得分:1)
在完成对象之后,Phantom Reference才会出现在ReferenceQueue中。你正在做一个繁忙的循环,所以它是有问题的。请注意,最终确定至少需要两个gcs。
答案 2 :(得分:1)
我刚试过我系统上发布的代码,即使经过两次System.gc()调用也无法正常工作。即使System.gc()在此处的GCPhantomThread类的while循环中调用它也不会终止。
在我看来,这里的问题是你正在创建的对象永远不会被放置在ReferenceQueue中,因为当GCPhantomThread运行时它甚至都没有幻像可达。 main()方法中对象的PhantomReference超出了范围,因此当您运行GCPhantomThread时,该对象甚至不能进行幻像访问。根据文档,对于要加入的幻影参考,最终确定和幻像可达性是必要的。
当我将幻像引用传递给GCPhantomThread时,它可以正常工作。在我的机器上,这段代码总是终止:
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class MyGCPhantom {
public static void main(String[] args) throws InterruptedException {
GCPhantomObject p = new GCPhantomObject();
ReferenceQueue phantomQueue = new ReferenceQueue();
PhantomReference pr = new PhantomReference(p, phantomQueue);
new GCPhantomThread(pr, phantomQueue, "Phantom").start();
p = null;
pr = null;
System.gc();
System.out.println("main thread done ...");
}
}
class GCPhantomObject {
@Override
protected void finalize() {
System.out.println("GCPhantom finalized at " + System.nanoTime());
}
}
class GCPhantomThread extends Thread {
private ReferenceQueue referenceQueue;
private String name;
private PhantomReference pr;
GCPhantomThread(PhantomReference pr, ReferenceQueue referenceQueue, String name) {
this.referenceQueue = referenceQueue;
this.name = name;
this.pr = pr;
}
@Override
public void run() {
try {
while (referenceQueue.remove(5000) == null) {
System.gc();
}
System.out.println(name + " found at " + System.nanoTime());
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
答案 3 :(得分:0)
Jack Shirazi发表了一篇关于Finalizer的精彩文章。一旦我们浏览了这篇文章,问题就会自行解决。
http://www.fasterj.com/articles/finalizer1.shtml
简而言之: 在非常重要的finalize()方法的情况下,即使收集对象&#39;在第一次GC运行中,当时没有物理删除。这发生在下一次GC运行中。这就是PR对象在第二次GC期间出现在队列中的原因。