Java WeakHashMap и кэширование: почему он ссылается на ключи, а не на значения?


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

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

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

4 58

4 ответа:

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

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

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

WeakHashMap<Thread, SomeMetaData>

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

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

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

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

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

new MapMaker().weakValues().expiration(5, TimeUnit.MINUTES).makeMap();

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

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

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

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

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

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

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

$ 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();
    }
}