Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

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

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

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

WeakHashMap<Thread, SomeMetaData>

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

Подробнее см. WeakHashMap в кеше!.

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

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

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

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

Ответ 2

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

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

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

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

Ответ 3

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

Ответ 4

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

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

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

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