Карта фиксированной памяти Java

Есть ли простая и эффективная реализация Map, которая позволяет ограничить память, используемую картой.

Мой вариант использования заключается в том, что я хочу динамически выделять большую часть памяти, доступной на момент ее создания, но я не хочу OutOFMemoryError когда-либо в будущем. По сути, я хочу использовать эту карту в качестве кеша, но я хочу избегать сложных реализаций кеширования, таких как EHCache. Моя потребность проста (максимум алгоритм LRU)

Я должен дополнительно пояснить, что объекты в моем кэше - это char[] или аналогичные примитивы, которые не будут содержать ссылок на другие объекты.

Я могу установить верхний предел максимального размера для каждой записи.


person juber    schedule 31.05.2010    source источник
comment
Насколько я понимаю ваш вопрос, ваша идея состоит в том, чтобы при запуске выделить выделенный объем памяти для кеширования. Проблема в том, что вы не можете заранее узнать тип памяти (ведь мы ведь говорим о кешировании объектов?). В отличие от C, вы не можете просто выделить блок памяти, а затем преобразовать указатель в его часть, которая будет рассматриваться как объект X. Таким образом, для обобщенных решений кеши фиксированной памяти нецелесообразны в Java. Также ваше предположение, что это может защитить вас от OutOfMemoryErrors, в лучшем случае сомнительно.   -  person Durandal    schedule 31.05.2010


Ответы (5)


Вы можете использовать LinkedHashMap, чтобы ограничить количество записей в Map:

removeEldestEntry(Map.Entry<K,V> eldest): возвращает true, если эта карта должна удалить самую старую запись. Этот метод вызывается put и putAll после вставки новой записи в карту. Он предоставляет разработчику возможность удалять самую старую запись каждый раз, когда добавляется новая. Это полезно, если карта представляет кеш: это позволяет карте уменьшить потребление памяти за счет удаления устаревших записей.

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

private static final int MAX_ENTRIES = 100;

protected boolean removeEldestEntry(Map.Entry eldest) {
    return size() > MAX_ENTRIES;
}

Связанные вопросы

person polygenelubricants    schedule 31.05.2010
comment
WeakHashMap удалит запись, если не будет сильной ссылки на ключ. Похоже, это не подходит для кеширования. Также, если к клавише удерживается достаточно сильная ссылка, вы все равно увидите OOME. Поэтому я не понимаю, как здесь может помочь WeakHashMap .. - person Enno Shioji; 31.05.2010
comment
@Zwei: Я просто оставил это там как возможность; Я удалил его сейчас, оставив исключительно LinkedHashMap в качестве опции. - person polygenelubricants; 31.05.2010
comment
@juber: в одном из связанных вопросов упоминается org.apache.commons.collections.map.LRUMap; Вы можете это проверить. На самом деле я с этим не знаком. - person polygenelubricants; 03.06.2010

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

Напротив, SoftReference более тесно связан с распределением памяти. Подробнее о различиях см. Нет SoftHashMap?.

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

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

Чтобы получить семантику LRU для заданного числа ссылок N, поддерживайте список последних N записей, извлеченных из кеша - когда запись извлекается из кеша, она добавляется в начало списка (а конец списка удаляется. .) Чтобы гарантировать, что это не удерживает слишком много памяти, вы можете создать мягкую ссылку и использовать ее в качестве триггера для удаления некоторого процента записей из конца списка. (И создайте новую мягкую ссылку для следующего триггера.)

person mdma    schedule 31.05.2010

Решения платформы Java

Если все, что вам нужно, это карта, ключи которой можно очистить, чтобы избежать OutOfMemoryErrors, вы можете изучить WeakHashMap. Он использует WeakReferences, чтобы разрешить сборщику мусора собрать записи карты. Однако он не будет применять какую-либо семантику LRU, кроме тех, которые присутствуют в сборке мусора поколений.

Также существует LinkedHashMap, в котором это документация:

Предоставляется специальный конструктор для создания связанной хэш-карты, порядок итерации которой соответствует порядку последнего доступа к ее записям, от наименее недавно обращавшегося к последнему (порядок доступа). Этот вид карты хорошо подходит для создания кэшей LRU. Вызов метода put или get приводит к доступу к соответствующей записи (при условии, что она существует после завершения вызова). Метод putAll создает одну запись доступа для каждого сопоставления в указанной карте в том порядке, в котором сопоставления "ключ-значение" предоставляются итератором набора записей указанной карты. Никакие другие методы не генерируют доступ к входу. В частности, операции с представлениями-коллекциями не влияют на порядок итерации вспомогательной карты.

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

Сделайте свое собственное решение

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

person jasonmp85    schedule 31.05.2010
comment
Я также только что наткнулся на Google Collections MapMaker: google-collections.googlecode.com/svn/trunk/javadoc/com/google/. Выглядит довольно полезно. - person jasonmp85; 03.06.2010

WeakHashMap не обязательно достигнет вашей цели, поскольку, если ваше приложение удерживает достаточно сильную ссылку на ключи, вы УВИДИТЕ OOME.

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

Я рекомендую использовать простую карту LRU, например http://commons.apache.org/collections/apidocs/org/apache/commons/collections/LRUMap.html.

person Enno Shioji    schedule 31.05.2010

спасибо за ответы ребята!

Как указал jasonmp85, у LinkedHashMap есть конструктор, который разрешает порядок доступа. Я пропустил этот бит, когда смотрел документацию по API. Реализация также выглядит достаточно эффективной (см. Ниже). В сочетании с ограничением максимального размера для каждой записи это должно решить мою проблему.

Я также внимательно посмотрю на SoftReference. Для справки: у Google Collections есть довольно хороший API для SoftKeys, SoftValues ​​и карт в целом.

Вот фрагмент из класса Java LikedHashMap, который показывает, как они поддерживают поведение LRU.

    /**
     * Removes this entry from the linked list.
     */
    private void remove() {
        before.after = after;
        after.before = before;
    }

    /**
     * Inserts this entry before the specified existing entry in the list.
     */
    private void addBefore(Entry<K,V> existingEntry) {
        after  = existingEntry;
        before = existingEntry.before;
        before.after = this;
        after.before = this;
    }

    /**
     * This method is invoked by the superclass whenever the value
     * of a pre-existing entry is read by Map.get or modified by Map.set.
     * If the enclosing Map is access-ordered, it moves the entry
     * to the end of the list; otherwise, it does nothing.
     */
    void recordAccess(HashMap<K,V> m) {
        LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
        if (lm.accessOrder) {
            lm.modCount++;
            remove();
            addBefore(lm.header);
        }
person juber    schedule 02.06.2010
comment
Видимо да. Ну что ж. По крайней мере, у меня есть привычка голосовать за всех остальных по темам, в которых я участвую (при условии, что их ответы хоть как-то полезны, а не абсолютно неверны). - person jasonmp85; 03.06.2010
comment
извините, ребята, я впервые задаю вопрос на StackOverflow. Мне не разрешили голосовать, так как я не зарегистрирован. Я принял свой ответ, потому что в нем я суммировал ответы, которые я нашел полезными. И Джосон, да, ваш ответ был очень полезным, спасибо. - person juber; 03.06.2010