Статический Map как кэш — рабочий код до утечки памяти

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

public class PriceService {

private static final Map CACHE = new HashMap<>();

public BigDecimal getPrice(String sku) { return CACHE.computeIfAbsent( sku, this::loadFromDb); } }

В тестах и на старте прода всё работает: ключей мало, память не заканчивается. Проблема появляется, когда ключи — это не фиксированный список SKU, а что-то растущее без границы: id пользователя, токен сессии, комбинация параметров запроса.

Map растёт, ничего не вытесняется, объекты в значениях живут в Old Gen до конца жизни процесса. GC-паузы постепенно растут, потому что сборщик честно проходит по всё большему живому графу объектов, пока не долетает до OutOfMemoryError или до перезапуска пода по памяти.

На ревью в такой момент спросят про eviction policy — maximumSize, expireAfterAccess, expireAfterWrite. Ожидают Caffeine или Guava Cache вместо голого HashMap. Если предложить WeakHashMap как решение — это не то же самое: там очищается запись, только если сам объект-ключ не удерживается больше нигде, а не по политике размера или времени.

Кэш без границы — это не кэш, это утечка с отложенным сроком.

Тренажёр: 600 вопросов, мок с таймером, план повторов

senior·base — что спрашивают на самом деле


В этом посте были ссылки, но мы их удалили по правилам Сетки