2016-01-19 2 views
4

Ключевой объект в WeakHashMap стал слабодоступным. И карта должна быть удалена после GC. Но сильная ссылка на объект ценности остается. Зачем?Почему WeakHashMap содержит сильную ссылку на значение после GC?

Такое же поведение наблюдается и с картами слайков гуавы.

Ожидаемый результат:

... 
refKey.get = null 
refValue.get = null 

Но я получаю выход:

map.keys = [] 
map.values = [] 
map.size = 0 
refKey.get = null 
refValue.get = (123) 

Код:

import java.lang.ref.WeakReference; 
import java.util.Map; 
import java.util.WeakHashMap; 
import com.google.common.collect.MapMaker; 

public class Test { 

    static class Number { 
     final int number; 
     public Number(int number) { this.number = number; } 
     public String toString() { return "(" + number + ")"; } 
    } 

    static class Key extends Number { 
     public Key(int number) { super(number); } 
    } 

    static class Value extends Number { 
     public Value(int number) { super(number); } 
    } 

    public static void main(String args[]) { 

     //Map<Key, Value> map = new MapMaker().weakKeys().makeMap(); 
     Map<Key, Value> map = new WeakHashMap<>(); 

     Key key = new Key(1); 
     Value value = new Value(123); 

     map.put(key, value); 

     WeakReference<Key> refKey = new WeakReference<>(key); 
     WeakReference<Value> refValue = new WeakReference<>(value); 

     key = null; 
     value = null; 

     System.gc(); 

     System.out.println("map.keys = " + map.keySet()); 
     System.out.println("map.values = " + map.values()); 
     System.out.println("map.size = " + map.size()); 
     System.out.println("refKey.get = " + refKey.get()); 
     System.out.println("refValue.get = " + refValue.get()); 

    } 

} 

UPD:

я попытался ре rform GC в jConsole и jcmd, но результат не изменился.

+0

@Thilo В этом случае я получаю вывод: 'refKey.get() = (1)'. Но 'refKey.get() = null'. – Rumter

+0

Но это результат, который вы можете получить, когда сбор мусора производится только наполовину, нет? – Thilo

+0

Вы пытались jconsole или jcmd, чтобы увидеть, изменит ли это результат (вам придется спать достаточно долго, чтобы вы могли вписаться в это). – Thilo

ответ

4

WeakHashMap содержит Map.Entry экземпляры, которые ссылаются на ключ, используя WeakReference (на самом деле, в OpenJDK/Oracle JDK, it directly extends WeakReference).

Когда GC происходит, записи, которые теперь ссылаются на отсутствующие ключи, не волшебным образом удалены с карты: они все еще присутствуют до тех пор, пока не будут очищены, поэтому значение все еще присутствует и еще не собрано.

В OpenJDK, что происходит в expungeStaleEntries() с использованием ReferenceQueue, и что метод вызывается из нескольких мест:

  • size()
  • resize()
  • getTable() который сам вызывается из нескольких методов, включая get() и put()

Если вы хотите, чтобы ваша ценность была сборной, вы должны взаимодействовать с WeakHashMap, например. запросив его size() или выполнив поиск.

Отметьте, что это означает, что значение не может быть собрано до секунд сбор мусора.

Если я правильно помню, он работает более или менее одинаково в Гуаве.

+1

Это своего рода хромой ... Должно быть упомянуто в JavaDocs (аналогично тому, о чем он говорит, косвенно ссылаясь на ключ от значения). – Thilo

+0

Re: "lame". OTOH, это, вероятно, вообще не проблема в реальной жизни, поскольку чистка происходит всякий раз, когда вы делаете что-либо с картой. Еще ... можно было упомянуть в Javadocs. – Thilo

+1

@Thilo Да, это может быть более явным в Javadoc для людей, незнакомых с процессом сбора мусора. Если вы знакомы с ним, вы знаете, что записи не могут волшебным образом исчезнуть без какого-либо взаимодействия с картой. Он немного более явчен в Javadoc «MapMaker», хотя и не полностью. –

1

У вас нет контроля над GC в java. ВМ решает. Я никогда не сталкивался с ситуацией, когда требуется System.gc(). Поскольку вызов System.gc() просто ПРЕДЛАГАЕТ, что виртуальная машина делает сборку мусора, а также делает FULL сбор мусора (старые и новые поколения в многопользовательской куче), тогда это может фактически привести к потреблению более частых циклов процессора необходимо.

В JDK 1.7 и далее вы можете использовать следующую команду, чтобы заставить дс запустить

jcmd <pid> GC.run 

Get ProcessID с помощью jps

+0

Это не отвечает на вопрос. –

+0

n jdk 1.7, вы можете использовать команду ниже, чтобы заставить gc запускаться jcmd GC.run Вы можете получить идентификатор процесса с помощью jps. –

2

Запуск этой модификации кода и заставить GC в Jconsole подтверждает удаление ссылки.

System.gc(); 

    try { 
     while (refValue.get() != null) { 
      System.out.println("map.keys = " + map.keySet()); 
      Thread.sleep(5000); 
     } 
    } catch (InterruptedException e) { 
     e.printStackTrace(); 
    } 

    System.out.println("map.keys = " + map.keySet()); 
    System.out.println("map.values = " + map.values()); 
    System.out.println("map.size = " + map.size()); 
    System.out.println("refKey.get = " + refKey.get()); 
    System.out.println("refValue.get = " + refValue.get()); 

По некоторым причинам, хотя, если map.keySet() не запрашивается в петле, она никогда не заканчивается, что refValue не станет null.

+2

Если вы посмотрите на источник для WeakHashMap, существует метод 'expungeStaleEntries', который вызывается, когда вы запрашиваете такие вещи, как' size' или 'keySet'. Предположительно, это избавляет от ссылки на значение (и до тех пор есть жесткая ссылка на него). – Thilo

Смежные вопросы