2013-08-05 20 views
6

Próbuję zaimplementować mechanizm, który usuwa buforowane pliki, gdy obiekty, które je przechowują, i zdecydował się użyć PhantomReference s, aby otrzymać powiadomienie o wyrzuceniu śmieci z obiektu. Problem polega na tym, że wciąż doświadczam dziwnego zachowania się ReferenceQueue. Kiedy zmieniam coś w moim kodzie, nagle nie pobiera już obiektów. Więc starałem się uczynić ten przykład do testowania, i wpadł na ten sam problem:Dlaczego moje obiekty nie umierają?

public class DeathNotificationObject { 
    private static ReferenceQueue<DeathNotificationObject> 
      refQueue = new ReferenceQueue<DeathNotificationObject>(); 

    static { 
     Thread deathThread = new Thread("Death notification") { 
      @Override 
      public void run() { 
       try { 
        while (true) { 
         refQueue.remove(); 
         System.out.println("I'm dying!"); 
        } 
       } catch (Throwable t) { 
        t.printStackTrace(); 
       } 
      } 
     }; 
     deathThread.setDaemon(true); 
     deathThread.start(); 
    } 

    public DeathNotificationObject() { 
     System.out.println("I'm born."); 
     new PhantomReference<DeathNotificationObject>(this, refQueue); 
    } 

    public static void main(String[] args) { 
     for (int i = 0 ; i < 10 ; i++) { 
      new DeathNotificationObject();     
     } 
     try { 
      System.gc();  
      Thread.sleep(3000); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
    } 
} 

wyjście jest:

I'm born. 
I'm born. 
I'm born. 
I'm born. 
I'm born. 
I'm born. 
I'm born. 
I'm born. 
I'm born. 
I'm born. 

trzeba dodawać, że zmiana czasu sleep, nazywając gc wielokrotnie itd nie działa.

UPDATE

Jak sugeruje, zadzwoniłem Reference.enqueue() mojego odniesienia, który rozwiązał problem.

Najdziwniejsze jest to, że mam trochę kodu, który działa idealnie (po prostu go przetestowałem), chociaż nigdy nie wywołuje enqueue. Czy możliwe jest, że wstawienie w jakiś magiczny sposób zmaterializowało odniesienie?

public class ElementCachedImage { 
    private static Map<PhantomReference<ElementCachedImage>, File> 
      refMap = new HashMap<PhantomReference<ElementCachedImage>, File>(); 
    private static ReferenceQueue<ElementCachedImage> 
      refQue = new ReferenceQueue<ElementCachedImage>(); 

    static { 
     Thread cleanUpThread = new Thread("Image Temporary Files cleanup") { 
      @Override 
      public void run() { 
       try { 
        while (true) { 
         Reference<? extends ElementCachedImage> phanRef = 
           refQue.remove(); 
         File f = refMap.remove(phanRef); 
         Calendar c = Calendar.getInstance(); 
         c.setTimeInMillis(f.lastModified()); 
         _log.debug("Deleting unused file: " + f + " created at " + c.getTime()); 
         f.delete(); 
        } 
       } catch (Throwable t) { 
        _log.error(t); 
       } 
      } 
     }; 
     cleanUpThread.setDaemon(true); 
     cleanUpThread.start(); 
    } 

    ImageWrapper img = null; 

    private static Logger _log = Logger.getLogger(ElementCachedImage.class); 

    public boolean copyToFile(File dest) { 
     try { 
      FileUtils.copyFile(img.getFile(), dest); 
     } catch (IOException e) { 
      _log.error(e); 
      return false; 
     } 
     return true; 
    } 

    public ElementCachedImage(BufferedImage bi) { 
     if (bi == null) throw new NullPointerException(); 
     img = new ImageWrapper(bi); 
     PhantomReference<ElementCachedImage> pref = 
       new PhantomReference<ElementCachedImage>(this, refQue); 
     refMap.put(pref, img.getFile()); 

     new Thread("Save image to file") { 
      @Override 
      public void run() { 
       synchronized(ElementCachedImage.this) { 
        if (img != null) { 
         img.saveToFile(); 
         img.getFile().deleteOnExit(); 
        } 
       } 
      } 
     }.start(); 
    } 
} 

Niektóre filtrowane wyjścia:

2013-08-05 22: 35: 01.932 DEBUG Zapisz obraz do pliku: <> \ AppData \ Local \ Temp \ tmp7..0.PNG

2013-08-05 22: 35: 03,379 DEBUG Usuwanie nieużywanego pliku: <> \ AppData \ Local \ Temp \ tmp7..0.PNG utworzono w Pon Aug 05 22:35:02 IDT 2013

+0

Niczego nie kupisz. –

+0

Więc powinienem zadzwonić 'enqueue()' ... rozumiem, dzięki! – Elist

+0

Zobacz moją odpowiedź, ostatnia linia. –

Odpowiedz

6

Odpowiedź brzmi: że w twoim przykładzie sam PhantomReference jest nieosiągalny, a zatem śmieci zebrane przed obiektem, do którego się odnosi, są śmieci zebrane. Tak więc w momencie, gdy obiekt jest GCed, nie ma już więcej Reference, a GC nie wie, że powinien coś gdzieś umieścić.

To oczywiście jest jakiś wyścig head-to-head :-)

Wyjaśnia to również (bez patrząc głęboko w nowym kodem) dlaczego umieszczenie odniesienia do jakiegoś osiągalnego kolekcji sprawia przykładową pracę.

Dla celów informacyjnych (kalambur przeznaczony) tutaj jest zmodyfikowana wersja twojego pierwszego przykładu, który działa (na moim komputerze :-) Właśnie dodałem zestaw zawierający wszystkie referencje.

import java.lang.ref.PhantomReference; 
import java.lang.ref.Reference; 
import java.lang.ref.ReferenceQueue; 
import java.util.HashSet; 
import java.util.Set; 

public class DeathNotificationObject { 
    private static ReferenceQueue<DeathNotificationObject> refQueue = new ReferenceQueue<DeathNotificationObject>(); 
    private static Set<Reference<DeathNotificationObject>> refs = new HashSet<>(); 

    static { 
     Thread deathThread = new Thread("Death notification") { 
      @Override 
      public void run() { 
       try { 
        while (true) { 
         Reference<? extends DeathNotificationObject> ref = refQueue.remove(); 
         refs.remove(ref); 
         System.out.println("I'm dying!"); 
        } 
       } catch (Throwable t) { 
        t.printStackTrace(); 
       } 
      } 
     }; 
     deathThread.setDaemon(true); 
     deathThread.start(); 
    } 

    public DeathNotificationObject() { 
     System.out.println("I'm born."); 
     PhantomReference<DeathNotificationObject> ref = new PhantomReference<DeathNotificationObject>(this, refQueue); 
     refs.add(ref); 
    } 

    public static void main(String[] args) { 
     for (int i = 0 ; i < 10 ; i++) { 
      new DeathNotificationObject();     
     } 
     try { 
      System.gc();  
      Thread.sleep(3000); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
    } 
} 

Aktualizacja

Wywołanie enqueue ręcznie jest możliwa w Twojej przykład, ale nie w prawdziwym kodzie. daje czysty zły wynik.Pokażę wywołując enqueue w konstruktorze i stosując inny main:

public DeathNotificationObject() { 
    System.out.println("I'm born."); 
    PhantomReference<DeathNotificationObject> ref = new PhantomReference<DeathNotificationObject>(this, refQueue); 
    ref.enqueue(); 
} 

public static void main(String[] args) throws InterruptedException { 

    for (int i = 0 ; i < 5 ; i++) { 
     DeathNotificationObject item = new DeathNotificationObject(); 

     System.out.println("working with item "+item); 
     Thread.sleep(1000); 
     System.out.println("stopped working with item "+item); 
     // simulate release item 
     item = null; 
    } 

    try { 
     System.gc();  
     Thread.sleep(3000); 
    } catch (InterruptedException e) { 
     e.printStackTrace(); 
    } 
} 

Wyjście będzie tak:

I'm born. 
I'm dying! 
working with item [email protected] 
stopped working with item [email protected] 

Co oznacza, że ​​cokolwiek chce zrobić z kolejki odniesienia będzie zrobione kiedy przedmiot jest wciąż żywy.

+0

Przeczytam to bardzo dokładnie później. –

+0

masz rację, "kolejka" nie jest tu dobra! Dzięki – Elist

Powiązane problemy