5

PR、PRが指すオブジェクトO、およびPR用に設定されたRQがあります。RQをポーリングし続けるスレッドがあり、RQで最初に見つかった参照で、スレッドはそれを見つけた時刻を出力して終了します。

物事は正常に機能しますが、Oがファイナライズを行うと(どんなに些細なことでも)、スレッドはRQで参照を検出しなくなり、無期限に実行を続けます。

質問:なぜそれがこのように起こるのですか?私はSunJDK1.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

のSOPのコメントを外すだけfinalize()ですGCPhantomObject

4

4 に答える 4

4

あなたの分析はややずれています。良い場合と悪い場合の両方で、オブジェクトはを実装しfinalizeます。良いケースでは、それは簡単に実装されます。悪い場合には、自明ではありません。したがって、明らかな問題は、の自明な実装と自明でない実装の違いにありfinalizeます。

JVMが仕様によって参照をエンキューするように強制される理由はわかりません。GCを1回実行してから、何かが発生するのを待ち続けます。重要なファイナライザーがオブジェクトを復活させる可能性があることが知られているため、オブジェクトがキューに入れられるまでにさらに多くのGCサイクルが必要になる場合があります。GC呼び出しをさらに追加することをお勧めします。

pollまた、代わりに使用することを決定することremoveはお勧めしません。ビジーポーリングを防ぐために、ブロッキング呼び出しを使用する必要があります。

参考までに、これらはドキュメントからの関連する定義です。

ガベージコレクターが、ある時点でファントム参照の指示対象がファントム到達可能であると判断した場合、その時点または後で参照をキューに入れます。


オブジェクトは、強く、柔らかく、または弱く到達可能でなく、ファイナライズされており、一部のファントム参照で参照されている場合、ファントム到達可能です。


ファイナライズされたオブジェクトのファイナライザーが自動的に呼び出されました。

于 2012-10-17T11:45:36.093 に答える
1

ファントム参照は、オブジェクトがファイナライズされるまで、ReferenceQueueに表示されません。ビジーループを実行しているため、問題があります。ファイナライズには少なくとも2つのgcsが必要であることに注意してください。

于 2012-10-17T11:32:28.153 に答える
1

ここに投稿されたコードをシステムに試したところ、System.gc()を2回呼び出しても機能しません。ここでGCPhantomThreadクラスのwhileループにSystem.gc()呼び出しを配置し​​ても、終了しません。

ここでの問題は、GCPhantomThreadの実行中にファントムに到達することさえできないため、作成しているオブジェクトがReferenceQueueに配置されないことです。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();
            }
        }
    }

于 2012-10-19T14:07:44.790 に答える
0

ジャックシラジによるファイナライザーに関する優れた記事を見つけました。記事を読むと、質問はそれ自体に答えます。

http://www.fasterj.com/articles/finalizer1.shtml

簡単に説明すると、重要なfinalize()メソッドの場合、オブジェクトは最初のGC実行で「収集」されますが、その時点では物理的に削除されません。これは、次のGCの実行時に発生します。そのため、PRオブジェクトは2番目のGC中にキューに表示されます。

于 2017-10-31T14:05:15.027 に答える