2014-09-23 4 views
1

Я пытаюсь использовать ReferenceQueue для освобождения ресурсов, используемых объектами сбора мусора. Проблема в том, что моя ссылочная очередь всегда пуста, даже если есть доказательство того, что один из ссылочных объектов был собран мусором. Вот очень простой, автономный тест JUnit, который иллюстрирует то, что я пытаюсь сделать (отслеживать удаление объекта):Почему ReferenceQueue всегда пуст?

@Test 
public void weakReferenceTest() { 
    ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>(); 
    Object myObject1 = new Object(); 
    Object myObject2 = new Object(); 
    WeakReference<Object> ref1 = new WeakReference<Object>(myObject1, refQueue); 
    WeakReference<Object> ref2 = new WeakReference<Object>(myObject2, refQueue); 
    myObject1 = null; 

    // simulate the application running and calling GC at some point 
    System.gc(); 

    myObject1 = ref1.get(); 
    myObject2 = ref2.get(); 
    if (myObject1 != null) { 
     System.out.println("Weak Reference to MyObject1 is still valid."); 
     fail(); 
    } else { 
     System.out.println("Weak Reference to MyObject1 has disappeared."); 
    } 
    if (myObject2 != null) { 
     System.out.println("Weak Reference to MyObject2 is still valid."); 
    } else { 
     System.out.println("Weak Reference to MyObject2 has disappeared."); 
     fail(); 
    } 
    Reference<? extends Object> removedRef = refQueue.poll(); 
    boolean trackedRemoval = false; 
    while (removedRef != null) { 
     if (removedRef == ref1) { 
      System.out.println("Reference Queue reported deletion of MyObject1."); 
      trackedRemoval = true; 
     } else if (removedRef == ref2) { 
      System.out.println("Reference Queue reported deletion of MyObject2."); 
      fail(); 
     } 
     removedRef = refQueue.poll(); 
    } 
    if (trackedRemoval == false) { 
     fail(); 
    } 
} 

Для меня это всегда печатает:

Weak Reference to MyObject1 has disappeared. 
Weak Reference to MyObject2 is still valid. 

.. . Это нормально, но тест всегда терпит неудачу из-за того, что trackedRemoval находится на false в конце - ReferenceQueue всегда пуст.

Я использую ReferenceQueue и/или WeakReference s неправильно? Я также попробовал PhantomReference s, но это не имеет значения.

Интересно, что если вы конвертируете Unit Test в обычный метод public static void main(String[] args), то он работает как шарм!

Может ли кто-нибудь объяснить это конкретное поведение? Я давно искал ответ на этот вопрос.

+1

«В то же время или * в какое-то позднее время * он будет выставлять в очередь те недавно очищенные слабые ссылки, которые зарегистрированы в контрольных очередях». Это означает, что JVM разрешено делать крайне сумасшедшие вещи, помещая ссылку в ссылочную очередь. – hexafraction

+0

IIRC it * обычно * происходит, когда GC снова вызывается. Причина - это, скорее всего, эффективность, но я не знаю точного механизма. – maaartinus

+0

@hexafraction: Вы правы, теоретически это может потребоваться навсегда для того, чтобы объект был помещен в Очередь ссылок. Но опять же, для чего еще нужен этот механизм? – Alan47

ответ

2

Мне кажется, состояние гонки. Когда GC определяет, что объект, на который ссылается myObject1, является GCable, он будет GC его и очистит от WeakReference. Затем он добавит, что WeakReference в «список ожидающих ссылок». Существует поток обработчика ссылок, который будет удаляться из этого списка и добавляется к соответствующему ReferenceQueue. Выше деталь реализации, которая поддерживает JavaDoc

В то же время или через некоторое время он будет епдиеей эти вновь освобоженных слабых ссылками, которые зарегистрированы с эталонными очередей.

Когда код достигает

Reference<? extends Object> removedRef = refQueue.poll(); 

ссылка обработчик поток не должен быть добавлен ваш WeakReference к ReferenceQueue и поэтому poll возвращается null.

+0

Это звучит разумно, учитывая, что GC обычно работает в своей собственной нити. Однако, если это действительно так, то для чего нужны ReferenceQueues? Действительно ли авторы класса ReferenceQueue не рассматривали это? – Alan47

+1

@ Alan47 Я только что понял, что я никогда не отвечал на ваш комментарий. 'ReferenceQueue' был разработан точно так, как указано в документации API. Вы использовали 'poll()', который является неблокирующим вызовом, который немедленно возвращается либо с помощью «Reference», либо с помощью «null». Используйте 'remove()', если вы хотите дождаться регистрации. –

0

Хорошо, это может быть не окончательный ответ, но, по крайней мере, он получает тест модуля от вопроса для правильной работы. Вместо обычной java.lang.ref.ReferenceQueue, я попытался использовать следующий класс:

public class MyReferenceQueue<T> { 

    private Set<WeakReference<T>> WeakReferences = new HashSet<WeakReference<T>>(); 

    public void enqueue(final WeakReference<T> reference) { 
     if (reference == null) { 
      throw new IllegalArgumentException("Cannot add reference NULL!"); 
     } 
     this.WeakReferences.add(reference); 
    } 

    public WeakReference<T> poll() { 
     Iterator<WeakReference<T>> iterator = this.WeakReferences.iterator(); 
     while (iterator.hasNext()) { 
      WeakReference<T> ref = iterator.next(); 
      T object = ref.get(); 
      if (object == null) { 
       iterator.remove(); 
       return ref; 
      } 
     } 
     return null; 
    } 
} 

Недостатками этого подхода являются:

  • нужно явно enqueue в WeakReference s отслеживать
  • работает только для WeakReference с , а не для PhantomReference (поскольку их метод get всегда возвращает null по определению)

При этом, модульный тест преуспевает по назначению.Кроме того, он не полагается на GC, чтобы делать что-либо в частности (например, добавление WeakReference в некоторую очередь), за исключением того, что он делает недействительным WeakReference (который он всегда делает надежно). Возможно, он делает что-то супер уродливое, о котором я не знаю, но пока он выполняет свою работу.

EDIT

Вот обновленный JUnit-тест, который использует мои пользовательские реализации.

public class WeakReferenceTest { 

    @Test 
    public void weakReferenceTest() { 
     MyReferenceQueue<Object> refQueue = new MyReferenceQueue<Object>(); 
     Object myObject1 = new Object(); 
     Object myObject2 = new Object(); 
     // note that the second constructor argument (the reference queue) is gone... 
     WeakReference<Object> ref1 = new WeakReference<Object>(myObject1); 
     WeakReference<Object> ref2 = new WeakReference<Object>(myObject2); 
     // ... instead we enqueue the references manually now 
     refQueue.enqueue(ref1); 
     refQueue.enqueue(ref2); 
     // the rest of the test remains the same 
     myObject1 = null; 

     // simulate the application running and calling GC at some point 
     System.gc(); 

     myObject1 = ref1.get(); 
     myObject2 = ref2.get(); 
     if (myObject1 != null) { 
      System.out.println("Weak Reference to MyObject1 is still valid."); 
      fail(); 
     } else { 
      System.out.println("Weak Reference to MyObject1 has disappeared."); 
     } 
     if (myObject2 != null) { 
      System.out.println("Weak Reference to MyObject2 is still valid."); 
     } else { 
      System.out.println("Weak Reference to MyObject2 has disappeared."); 
      fail(); 
     } 
     Reference<? extends Object> removedRef = refQueue.poll(); 
     boolean trackedRemoval = false; 
     while (removedRef != null) { 
      if (removedRef == ref1) { 
       System.out.println("Reference Queue reported deletion of MyObject1."); 
       trackedRemoval = true; 
      } else if (removedRef == ref2) { 
       System.out.println("Reference Queue reported deletion of MyObject2."); 
       fail(); 
      } 
      removedRef = refQueue.poll(); 
     } 
     if (trackedRemoval == false) { 
      fail(); 
     } 
    } 

} 

Этот тест преуспевает по назначению.

+0

Я не вижу, как вы использовали свой собственный класс, где ожидается «ReferenceQueue». Обратите внимание, что 'HashSet' не сохраняет порядок. –

+0

@SotiriosDelimanolis Я отредактировал ответ выше, теперь он содержит обновленный JUnit-Test. И да, я знаю, что 'HashSet' не сохранит порядок элементов. Для этого сценария, я не очень забочусь о заказе. Однако, поскольку новый класс называется 'Queue', возможно,' SortedSet' будет более правильным способом его реализации. – Alan47

+1

Что бы вы сделали в случае тысяч ссылок? Вы должны пройти через все, так как вы не знаете, какой из них очищается. Вы должны делать это все время, так как вы не знаете, когда происходит GC. Но да, я могу представить, что это может быть полезно в некоторых сценариях (несколько дорогостоящих ресурсов, которые нужно очистить как можно скорее). – maaartinus