Принуждение освобождения большого объекта кеша в Java

Я использую большой (миллионный) hashmap записей для кэширования значений, необходимых алгоритму, ключ представляет собой комбинацию из двух объектов как длинных. Поскольку он постоянно растет (поскольку ключи на карте меняются, поэтому старые не нужны больше), было бы неплохо иметь возможность принудительно стереть все содержащиеся в нем данные и начать заново во время выполнения, есть ли способ сделать это эффективно в Java?

Я имею в виду освобождение связанной памяти (около 1-1,5 гб хэш-карты) и перезапуск из пустой хэш-карты.

Ответ 1

Вы можете позвонить HashMap.clear(). Это приведет к удалению всех данных. Обратите внимание, что это будет только отбрасывать все записи, но сохранить внутренний массив, используемый для хранения записей одного размера (вместо сокращения до начальной емкости). Если вам также необходимо устранить это, самым простым способом было бы отказаться от всего HashMap и заменить его новым экземпляром. Это, конечно, работает только если вы контролируете, у кого есть указатель на карту.

Что касается восстановления памяти, вы должны позволить сборщику мусора выполнять свою работу.

Являются ли ваши значения длинными? В этом случае вы можете захотеть взглянуть на более эффективную (память) чем общий HashMap, такой как TLongLongHashMap, найденный в библиотеке GNU Trove. Это должно сэкономить массу памяти.

Ответ 2

Похоже, вам нужен WeakHashMap:

Реализация Map на основе hashtable со слабыми ключами. Запись в WeakHashMap будет автоматически удалена, если ее ключ больше не используется обычным образом. Точнее, наличие отображения для данного ключа не будет препятствовать тому, чтобы ключ был отброшен сборщиком мусора, то есть был завершен, финализирован, а затем регенерирован. Когда ключ был отброшен, его запись эффективно удаляется с карты, поэтому этот класс ведет себя несколько иначе, чем другие реализации Map.

Я не уверен, как это работает с Long в качестве ключей. Кроме того, это может представлять интерес:

WeakHashMap не является кешем! Понимание WeakReference и SoftReference

Ответ 4

Для кэша, поддерживающего память, вы можете использовать коллекции Apache Commons, в частности их класс org.apache.commons.collections.map.ReferenceMap. Специальная операция Java - это мягкая ссылка. Java предоставляет WeakHashMap для слабых ссылок, но слабые ссылки - это не то, что вы хотите для кеша. Java не предоставляет SoftHashMap, но ReferenceMap из Apache Commons может быть работоспособной заменой.

Знание памяти о мягких ссылках несколько грубо и негибко. Вы можете играть с некоторыми вариантами Java, чтобы как-то их настроить, особенно значение -XX:SoftRefLRUPolicyMSPerMB, которое выражает (в миллисекундах), как долго сохраняются в памяти сохраненные в памяти значения софт-ссылок (когда они перестают быть доступными напрямую). Например, при этом:

java -XX:SoftRefLRUPolicyMSPerMB=2500

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

Если мягкие ссылки не обеспечивают то, что вы ищете, тогда вам придется реализовать свою собственную стратегию кэширования и, действительно, сбросить карту вручную. Это ваш первоначальный вопрос. Для промывки вы можете использовать метод clear() или просто создать новый HashMap. Разница должна быть небольшой, и у вас могут даже возникнуть проблемы, просто измеряя эту разницу.

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

import java.util.*;

public class Cache {

    private static final int MAX_SIZE = 500000;

    private Map[] backend;
    private int size = 0;

    public Cache(int n)
    {
        backend = new Map[n];
        for (int i = 0; i < n; i ++)
            backend[i] = new HashMap();
    }

    public int size()
    {
        return size;
    }

    public Object get(Object key)
    {
        for (Map m : backend) {
            if (m.containsKey(key))
                return m.get(key);
        }
        return null;
    }

    public Object put(Object key, Object value)
    {
        if (backend[0].containsKey(key))
            return backend[0].put(key, value);
        int n = backend.length;
        for (int i = 1; i < n; i ++) {
            Map m = backend[i];
            if (m.containsKey(key)) {
                Object old = m.remove(key);
                backend[0].put(key, value);
                return old;
            }
        }
        backend[0].put(key, value);
        size ++;
        while (size > MAX_SIZE) {
            size -= backend[n - 1].size();
            System.arraycopy(backend, 0, backend, 1, n - 1);
            backend[0] = new HashMap();
        }
        return null;
    }
}

Вышеприведенный код полностью не протестирован и должен быть расширен с помощью дженериков. Тем не менее, это иллюстрирует основные идеи: все карты проверяются при чтении (get()), все новые значения переходят на первую карту, общий размер поддерживается, а когда размер превышает заданный предел, карты вращаются. Обратите внимание, что есть специальное лечение, когда новое значение задается для известного ключа. Кроме того, в этой версии ничего не делается при поиске кешированного значения, но мы можем "омолодить" доступное кешированное значение: после get(), когда значение будет найдено, но не на первой карте, его можно перенести в первый карта. Таким образом, часто используемые значения будут сохраняться навсегда.

Ответ 5

Вы взглянули на WeakHashMap?

Ответ 6

Если у вас есть запасная память, вы можете реализовать кеш тайм-кода, в котором каждое значение в хэш-карте содержит ваше длинное значение и временную метку временной интерполяции в миллисекундах, а затем фоновый поток перебирает значения каждые X секунд и удаляет что-либо еще чем X секунд/миллисов.

Только мои 2 цента:)

Ответ 7

Вместо использования HashMap или другой реализации карты в качестве кеша вы можете попытаться использовать фреймворк, специализированный для кэширования. Хорошо известная структура кэширования для Java - Ehcache.

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