2017-03-15 3 views
1

クラス内のフィールドに参照対象がある場合、疑似参照に問題があります。クラスオブジェクトがnullに設定されている場合、フィールドはGCによって自動的に収集されていないクラス内のフィールドは、ファントム参照を介してGCによって収集されません。

Controller.java

public class Controller { 
     public static void main(String[] args) throws InterruptedException 
    { 
     Collector test = new Collector(); 
     test.startThread(); 

     Reffered strong = new Reffered(); 
     strong.register(); 
     strong = null; //It doesn't work 
     //strong.next =null; //It works 
     test.collect(); 
     Collector.m_stopped = true; 
     System.out.println("Done"); 
    } 
} 

Collector.java:私は、参照キューにオブジェクトを登録したときに、それを印刷しCollectorを持っています収集されます。

import java.lang.ref.PhantomReference; 
import java.lang.ref.Reference; 
import java.lang.ref.ReferenceQueue; 
import java.util.HashMap; 
import java.util.Map; 

public class Collector { 
     private static Thread m_collector; 
     public static boolean m_stopped = false; 
     private static final ReferenceQueue refque = new ReferenceQueue(); 
     Map<Reference,String> cleanUpMap = new HashMap<Reference,String>(); 
     PhantomReference<Reffered> pref; 


     public void startThread() { 
      m_collector = new Thread() { 
       public void run() { 
        while (!m_stopped) { 
         try { 
           Reference ref = refque.remove(1000); 
           System.out.println(" Timeout "); 
                if (null != ref) { 
           System.out.println(" ref not null "); 

          } 
         } catch (Exception ex) { 
          break; 
         } 
        } 
       } 
      }; 
      m_collector.setDaemon(true); 
      m_collector.start(); 
     } 

     public void register(Test obj) { 
      System.out.println("Creating phantom references"); 


       //Referred strong = new Referred(); 
       pref = new PhantomReference(obj, refque); 
       cleanUpMap.put(pref, "Free up resources"); 

     } 

     public static void collect() throws InterruptedException { 
     System.out.println("GC called"); 
     System.gc(); 
     System.out.println("Sleeping"); 
     Thread.sleep(5000); 
    } 
} 

Reffered.java

public class Reffered { 

     int i; 
     public Collector test; 
     public Test next; 

     Reffered() { 
      test= new Collector(); 
      next = new Test(); 

     } 
     void register() { 
      test.register(next); 
     } 
    } 

テストは空のクラスです。私は、Refferedオブジェクトがnullに設定されている場合、Refferredクラスの "next"フィールドは収集されないことがわかります。言い換えれば、「強い」がヌルに設定されている場合、「次の」は収集されません。私は、 "強い"がnullに設定されているときには、 "次へ"はもう参照されないので、GCによって自動的に "次へ"が収集されると仮定しました。しかし、 "strong.next"がnullに設定されていると、私たちが考えるように "next"が集められます。 strongがnullに設定されていると、「次へ」が自動的に収集されないのはなぜですか?

答えて

1

あなたは非常に混乱しているコード構造を持っています。

あなたのコードの開始時に、あなたはあなたがバックグラウンドスレッドがへの参照を持っていますCollectorのインスタンスを作成している

Collector test = new Collector(); 
test.startThread(); 

文を持っています。そのスレッドはその参照にも触れていませんが、匿名の内部クラスであるため、その外部インスタンスへの参照を保持します。あなたはコンストラクタでnew Collector()で初期化されたタイプCollectorのフィールドを持っている​​以内

は、言い換えれば、あなたはCollectorの別のインスタンスを作成しています。これは、registerを呼び出すインスタンスです。

そうもPhantomReferenceへの参照を有するregisterによって作成されたすべてのアーティファクト、pref及びcleanUpMapに保持HashMapに保持PhantomReferenceは、唯一​​によって参照Collectorのインスタンスによって参照されます。​​インスタンスが到達不能になると、これらの成果物はすべて到達不能になり、キューには何も登録されません。

これはjava.lang.ref package documentationを思い出すための場所です:

登録参照オブジェクトとそのキューの関係は一方的です。つまり、キューは、キューに登録されている参照を追跡しません。登録された参照が到達不能になると、それは決してエンキューされません。プログラムが参照対象に関心を持っている限り、オブジェクトが到達可能な状態を維持することを保証するのは、参照オブジェクトを使用するプログラムの責任です。

プログラムでこの問題を説明する方法はいくつかあります。
代わりに、いずれかのstrong = null;またはstrong.next = null;を行うので、あなたが両方行うことがあります。

ここ
strong.next = null; 
strong = null; 

を、それはnextが出てゼロにされたことは重要ではありませんstrong = nullが実行された後、この変数は、とにかく到達不能です。その後、​​インスタンス経由でしか到達できないPhantomReferenceは到達不能になり、 "ref not null"メッセージは出力されません。

また、あなたはまた、このようにキューに入れられたことはありません、PhantomReferenceが到達不能になります

strong.next = null; 
strong.test = null; 

にそのコードの一部を変更することがあります。

しかし、あなたは

Object o = strong.test; 
strong = null; 

に変更した場合oPhantomReferenceへの間接参照を保持しているように、メッセージの「参照nullではないが、」印刷されます。これは保証された動作ではなく、Javaが未使用のローカル変数の影響を排除できることを強調しなければなりません。現在のHotSpotの実装では、その点を示すために十分に再現性があります。


一番下の行は、予想通りTestインスタンスが常に収集された、です。場合によっては、PhantomReference自体を含めて、あなたが知っていた以上に多くの情報が収集されたため、通知は行われませんでした。

最後の発言として、2つのスレッド間で共有するpublic static boolean m_stoppedのような変数は、スレッドが別のスレッドによって行われた変更を確実に認識するように、volatileと宣言する必要があります。 JVMのオプティマイザは、このような短い実行プログラムやx68などのアーキテクチャではキャッシュを同期させるために多くの作業をしなかったため、ここではうまくいきません。しかし、それは信頼できません。

+0

ありがとう、@holger。 – GoT

関連する問題