Кэш LRU переменного размера

Я пытаюсь реализовать LRU cache на Java, который должен иметь возможность:
Динамически изменять размер. В том смысле, что я планирую иметь его как SoftReference подписанного на ReferenceQueue. Таким образом, в зависимости от потребления памяти размер кеша будет варьироваться.

Я планирую использовать ConcurrentHashMap, где значение будет мягкой ссылкой, а затем периодически очищать очередь для обновления карты.
Но проблема с вышеизложенным заключается в том, как мне сделать его LRU?

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


person Jatin    schedule 03.08.2012    source источник
comment
CacheBuilder в Guava должен работать. хорошо здесь, который смешивает функции LRU, полученные из ConcurrentLinkedHashMap.   -  person Ben Manes    schedule 04.08.2012


Ответы (3)


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

Как правило, более эффективно использовать некоторый подход, основанный на времени, с регулярными сильными ссылками, которые намного дешевле для виртуальной машины, чем подклассы ссылок (быстрее в обработке для программы и GC и не используют дополнительную память для самой ссылки.). т.е. освободить все объекты, которые не использовались в течение определенного времени. Вы можете проверить это с помощью периодического TimerTask, который вам в любом случае понадобится для работы с эталонной очередью. Идея состоит в том, что если для создания объекта требуется, например, 10 мс, и вы сохраняете его не более 1 с после его последнего использования, вы в среднем будете работать только на 1% медленнее, чем если бы вы сохраняли все объекты навсегда. Но поскольку он, скорее всего, будет использовать меньше памяти, на самом деле он будет быстрее.

Изменить. Один из способов реализовать это — использовать 3 корзины внутри. Объекты, помещенные в кеш, всегда вставляются в корзину 0. Когда объект запрашивается, кеш ищет его во всех 3 корзинах по порядку и помещает в корзину 0, если его там еще нет. TimerTask вызывается через фиксированные промежутки времени и просто удаляет корзину 2 и помещает новую пустую корзину в начало списка корзин, так что новая корзина 0 будет пустой, а предыдущая корзина 0 станет 1, а прежняя корзина 1 теперь станет корзиной. 2. Это гарантирует, что бездействующие объекты переживут как минимум один и не более двух интервалов таймера, а объекты, доступ к которым осуществляется более одного раза за интервал, будут очень быстро извлекаться. Общие накладные расходы на обслуживание такой структуры данных будут значительно меньше, чем все, что основано на ссылочных объектах и ​​ссылочных очередях.

person x4u    schedule 03.08.2012
comment
Я согласен с утверждением, что: одинаковая временная сложность будет использоваться для потока TimerTask или Reference, поскольку оба должны будут выполнять итерацию по одним и тем же элементам. А также, не играя много с памятью, она будет более предсказуемой и надежной. Но как насчет размера? Также Neo4J использует тот же механизм (github.com/neo4j/community/blob/master/kernel/src/main/java/org/), и они, кажется, очень довольны и гордятся этим. Вероятно, профилирование всех вышеперечисленных различных методов должно сделать его лучше. Но в целом я по-прежнему считаю, что Map с SoftRef должен быть лучшим. - person Jatin; 03.08.2012
comment
Если вы хотите ограничить общее количество кэшированных объектов с помощью реализации, которую я предложил выше, вы можете просто активировать ротацию корзины, когда вы достигнете предела общего размера после вставки нового объекта, и оставить установленный флаг для задания таймера, чтобы позволить он знает, что на этот раз ничего не нужно делать (таймер сбрасывает флаг). Это заставит ваш кеш автоматически переключаться из режима, основанного на времени, в режим ограниченного размера (и обратно). Вы также должны инициировать ротацию, если ведро 0 становится, т. е. вдвое меньше общего максимального размера, чтобы сохранить характеристику LRU кеша и улучшить его поведение в худшем случае. - person x4u; 03.08.2012

Ваш вопрос на самом деле не имеет смысла, если вы не хотите одновременно несколько таких кешей. Если у вас есть только один кеш, не ограничивайте его размер и всегда используйте WeakReference. Таким образом, кеш будет автоматически использовать всю доступную свободную память.

Приготовьтесь к горячим дискуссиям с вашими системными администраторами, так как они будут жаловаться на то, что в вашем приложении есть утечка памяти и «вылетит в любой момент!» вздыхает

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

person Aaron Digulla    schedule 03.08.2012
comment
Я думаю, что я не правильно передал это. Я не хочу переключаться между слабым и мягким. Что мне нужно, так это кеш с мягкими ссылками (чтобы он мог соответственно масштабироваться), который действует как кеш lru. - person Jatin; 03.08.2012
comment
Ах. Тогда вам придется использовать LinkedHashMap или написать свою собственную карту. Никакая другая карта в Java не может быть ограничена в размере. Guava, например, использует свои собственные CacheBuilder для создания кешей с определенными ограничениями, поскольку ничто в среде выполнения Java не предлагает достаточного контроля. - person Aaron Digulla; 03.08.2012
comment
Итак, в основном LinkedHashMap с некоторыми дополнительными усилиями, чтобы решить, что размер является лучшим подходом? - person Jatin; 03.08.2012
comment
Это подход, который требует наименьших усилий, но все же возвращает что-то полезное + LRU. Если вы можете жить без LRU, WeakHashMap тоже работает. - person Aaron Digulla; 03.08.2012

Я бы использовал LinkedHashMap, поскольку он поддерживает порядок доступа и используется в качестве карты LRU. Он может иметь переменный максимальный размер.

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

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

person Peter Lawrey    schedule 03.08.2012
comment
Проблема в том, что мне придется вручную изменить размер в зависимости от общего объема памяти, если LinkedHashMap. И что касается последнего утверждения, я не понимаю, почему это должно быть проблемой. - person Jatin; 03.08.2012
comment
Это только проблема, если вы ожидаете, что это будет иметь значение. ИМХО, я бы не добавлял сложности, которая не поможет. Невозможно узнать общий объем используемой памяти без выполнения полного GC, а затем невозможно принудительно удалить удаленные записи для освобождения без другого GC. - person Peter Lawrey; 03.08.2012
comment
Я думаю, что я не правильно передал это. Я не хочу переключаться между слабым и мягким. Что мне нужно, так это кеш с мягкими ссылками (чтобы он мог соответственно масштабироваться), который действует как кеш lru. - person Jatin; 03.08.2012
comment
Вы можете использовать LinkedHashMap<Key, SoftReference<Value>> ? - person Peter Lawrey; 03.08.2012
comment
Нет. Это все еще не сделает его LRU. Но да, это действительно продлит срок службы объектов из-за ограниченного размера карты (следовательно, lru на определенный период, но не навсегда). Но опять же, какой должен быть размер карты, останется более серьезной проблемой. PS: я отредактировал вопрос для большей ясности. - person Jatin; 03.08.2012
comment
LHM поддерживает LRU, устанавливая порядок доступа вместо порядка вставки. - person Peter Lawrey; 03.08.2012