WeakHashMap и кэширование в Java: почему он ссылается на ключи, а не на значения? - PullRequest
63 голосов
/ 26 ноября 2009

Java WeakHashMap часто упоминается как полезный для кэширования. Хотя кажется странным, что ее слабые ссылки определены в терминах ключей карты, а не ее значений. Я имею в виду, что это значения, которые я хочу кэшировать, и которые я хочу собирать мусором, когда никто, кроме кеша, строго ссылается на них, нет?

Каким образом это помогает удерживать слабые ссылки на ключи? Если вы делаете ExpensiveObject o = weakHashMap.get("some_key"), тогда я хочу, чтобы кеш удерживал 'o', пока вызывающая сторона больше не держит сильную ссылку, и мне наплевать на строковый объект "some_key".

Я что-то упустил?

Ответы [ 4 ]

108 голосов
/ 26 ноября 2009

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

WeakHashMap в основном полезен для хранения метаданных об объектах, жизненный цикл которых вы не контролируете. Например, если у вас есть группа объектов, проходящих через ваш класс, и вы хотите отслеживать дополнительные данные о них без необходимости уведомления об их выходе из области видимости и без ссылки на них, чтобы они оставались в живых.

Простой пример (и тот, который я использовал ранее) может выглядеть примерно так:

WeakHashMap<Thread, SomeMetaData>

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

См. WeakHashMap в не кеш! для получения дополнительной информации.

Для того типа кэша, который вам нужен, используйте специальную систему кэширования (например, EHCache ) или посмотрите google-collection ' класс MapMaker ; что-то вроде

new MapMaker().weakValues().makeMap();

сделает то, что вам нужно, или, если вы хотите стать модным, вы можете добавить время истечения:

new MapMaker().weakValues().expiration(5, TimeUnit.MINUTES).makeMap();
33 голосов
/ 26 ноября 2009

Основное использование для WeakHashMap - это когда у вас есть сопоставления, которые вы хотите исчезнуть, когда исчезнут их ключи. Кеш обратный - у вас есть сопоставления, которые вы хотите исчезнуть, когда исчезнут их значения.

Для кеша вы хотите Map<K,SoftReference<V>>. A SoftReference будет собираться сборщиком мусора при переполнении памяти. (Сравните это с WeakReference, который может быть очищен, как только не будет жесткой ссылки на его референт.) Вы хотите, чтобы ваши ссылки были мягкими в кеше (по крайней мере, в том, где сопоставления значения ключа don ' устарела), с тех пор есть вероятность, что ваши значения все еще будут в кеше, если вы будете искать их позже. Если бы ссылки были слабыми, ваши значения были бы gc'd сразу, победив цель кэширования.

Для удобства вам может потребоваться скрыть значения SoftReference внутри вашей реализации Map, чтобы ваш кэш имел тип <K,V> вместо <K,SoftReference<V>>. Если вы хотите сделать это, в этом вопросе есть предложения по реализации, доступные в сети.

Обратите внимание, что когда вы используете SoftReference значения в Map, вы должны сделать что-то, чтобы вручную удалить пары ключ-значение, для которых было очищено SoftReferences - иначе ваш Map будет только увеличиваться в размерах навсегда, а утечка памяти.

7 голосов
/ 20 июня 2011

Еще одна вещь, которую следует учитывать, это то, что если вы выберете подход Map<K, WeakReference<V>>, значение может исчезнуть, но сопоставление не будет. В зависимости от использования вы можете в результате получить Карту, содержащую много записей, чьи Слабые ссылки были GC'd.

6 голосов
/ 07 июня 2013

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

Слабые ссылки имеют возможность перемещать ссылку в очередь, когда на указанный объект больше нельзя получить доступ. Эта очередь должна быть очищена потоком очистки. А для очистки необходимо получить ключ для справки. По этой причине требуется вторая карта.

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

$ javac -Xlint:unchecked Cache.java && java Cache
{even: [2, 4, 6], odd: [1, 3, 5]}
{even: [2, 4, 6]}

Первая строка показывает содержимое кэша до удаления ссылки на нечетный список, а вторая строка после удаления коэффициентов.

Это код:

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class Cache<K,V>
{
    ReferenceQueue<V> queue = null;
    Map<K,WeakReference<V>> values = null;
    Map<WeakReference<V>,K> keys = null;
    Thread cleanup = null;

    Cache ()
    {
        queue  = new ReferenceQueue<V>();
        keys   = Collections.synchronizedMap (new HashMap<WeakReference<V>,K>());
        values = Collections.synchronizedMap (new HashMap<K,WeakReference<V>>());
        cleanup = new Thread() {
                public void run() {
                    try {
                        for (;;) {
                            @SuppressWarnings("unchecked")
                            WeakReference<V> ref = (WeakReference<V>)queue.remove();
                            K key = keys.get(ref);
                            keys.remove(ref);
                            values.remove(key);
                        }
                    }
                    catch (InterruptedException e) {}
                }
            };
        cleanup.setDaemon (true);
        cleanup.start();
    }

    void stop () {
        cleanup.interrupt();
    }

    V get (K key) {
        return values.get(key).get();
    }

    void put (K key, V value) {
        WeakReference<V> ref = new WeakReference<V>(value, queue);
        keys.put (ref, key);
        values.put (key, ref);
    }

    public String toString() {
        StringBuilder str = new StringBuilder();
        str.append ("{");
        boolean first = true;
        for (Map.Entry<K,WeakReference<V>> entry : values.entrySet()) {
            if (first)
                first = false;
            else
                str.append (", ");
            str.append (entry.getKey());
            str.append (": ");
            str.append (entry.getValue().get());
        }
        str.append ("}");
        return str.toString();
    }

    static void gc (int loop, int delay) throws Exception
    {
        for (int n = loop; n > 0; n--) {
            Thread.sleep(delay);
            System.gc(); // <- obstinate donkey
        }
    }

    public static void main (String[] args) throws Exception
    {
        // Create the cache
        Cache<String,List> c = new Cache<String,List>();

        // Create some values
        List odd = Arrays.asList(new Object[]{1,3,5});
        List even = Arrays.asList(new Object[]{2,4,6});

        // Save them in the cache
        c.put ("odd", odd);
        c.put ("even", even);

        // Display the cache contents
        System.out.println (c);

        // Erase one value;
        odd = null;

        // Force garbage collection
        gc (10, 10);

        // Display the cache again
        System.out.println (c);

        // Stop cleanup thread
        c.stop();
    }
}
...