← Назад к вопросам
Как определить из-за кэша ли происходит периодическая перезагрузка сервиса из-за OutOfMemoryError?
2.4 Senior🔥 141 комментариев
#JVM и управление памятью
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Диагностика OutOfMemoryError, вызванной кэшем
OutOfMemoryError из-за кэша — это частая проблема в production. Невозможно корректно очистить кэш или кэш растёт неконтролируемо, что приводит к исключению и перезагрузке сервиса.
1. Анализ Java GC логов
Первый шаг — посмотреть на GC логи при запуске приложения с флагами.
java -Xmx1024m \
-Xms512m \
-XX:+UseG1GC \
-XX:+PrintGCDetails \
-XX:+PrintGCDateStamps \
-XX:+PrintGCTimeStamps \
-Xloggc:/var/log/gc.log \
-XX:+UseGCLogFileRotation \
-XX:NumberOfGCLogFiles=10 \
-XX:GCLogFileSize=10M \
-jar application.jar
В логе ищи:
- Постоянный рост heap'а между Full GC сборками
- Всё более частые Full GC сборки
- Память не освобождается после Full GC (признак утечки)
2024-03-20T10:15:30.123+0000: 45.567: [Full GC (Allocation Failure) -- Heap before GC invocations=18:
par new generation total 680000K, used 679999K
eden space 598848K, 100% used
from space 81152K, 100% used
to space 81152K, 0% used
tenured generation total 1862528K, used 1862527K
...
Heap after GC invocations=18:
par new generation total 680000K, used 98304K
eden space 598848K, 0% used
from space 81152K, 98% used
to space 81152K, 0% used
tenured generation total 1862528K, used 1862527K --> ПРОБЛЕМА: стареющее поколение не освобождается
2. Heap Dump анализ
Вызови heap dump во время проблемы или перед перезагрузкой.
# Получить dump (PID процесса Java)
jmap -dump:live,format=b,file=heap.bin <PID>
# Или через JMX если включен
jcmd <PID> GC.heap_dump /var/dumps/heap.bin
Анализ в Eclipse MAT или JProfiler:
// Найди "Top consumers" - объекты, занимающие много памяти
// Обычно это:
// - Map entries (если используется неограниченный кэш)
// - String objects (если кэшируются строки)
// - Arrays (если кэшируются большие коллекции)
Основные шаги анализа:
- Откройте heap dump в MAT
- Перейдите в "Dominator Tree"
- Найдите классы, занимающие больше всего памяти
- Проверьте ссылки (incoming references)
- Найдите кэш-класс в цепочке ссылок
3. Практический пример: диагностика утечки кэша
public class CacheLeakExample {
// ПРОБЛЕМА: не ограниченный Map кэш
private static final Map<String, LargeObject> cache = new HashMap<>();
public static void main(String[] args) throws Exception {
// Симуляция: кэш растёт без ограничений
for (int i = 0; i < Integer.MAX_VALUE; i++) {
String key = "key_" + i;
cache.put(key, new LargeObject());
if (i % 10000 == 0) {
System.out.println("Cache size: " + cache.size());
}
}
}
static class LargeObject {
byte[] data = new byte[1024 * 1024]; // 1 MB
}
}
Диагностика:
- Heap dump покажет множество LargeObject в HashMap
- Incoming references: HashMap → CacheLeakExample (static field)
- Вывод: static Map без механизма очистки = утечка памяти
4. Типичные причины утечек в кэшах
1. Неограниченный размер кэша
// ПЛОХО
private static final Map<String, Object> cache = new HashMap<>();
public static void cacheValue(String key, Object value) {
cache.put(key, value); // Растёт бесконечно!
}
2. Отсутствие TTL (Time-To-Live)
// ПЛОХО: старые значения никогда не удаляются
private static final Map<String, CacheEntry> cache = new HashMap<>();
class CacheEntry {
Object value;
long createdAt;
}
3. Классическая утечка в Collection
// ПЛОХО: listener'ы не удаляются
private List<ChangeListener> listeners = new ArrayList<>();
public void addListener(ChangeListener listener) {
listeners.add(listener); // Никогда не удаляется!
}
4. Strong reference в кэше к объектам
// ПЛОХО: даже если объект больше не используется в коде,
// он живёт в кэше
public class DataCache {
private Map<Long, DataObject> cache = new HashMap<>();
public void cache(DataObject obj) {
cache.put(obj.getId(), obj);
}
}
5. Правильный кэш с ограничением
public class LimitedCache<K, V> {
private final LinkedHashMap<K, V> cache;
private final int maxSize;
public LimitedCache(int maxSize) {
this.maxSize = maxSize;
// LinkedHashMap с LRU эвикцией
this.cache = new LinkedHashMap<K, V>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > maxSize;
}
};
}
public synchronized void put(K key, V value) {
cache.put(key, value);
}
public synchronized V get(K key) {
return cache.get(key);
}
}
// Использование
LimitedCache<String, String> cache = new LimitedCache<>(10000);
cache.put("key", "value");
6. Использование правильных кэш-библиотек
// Google Guava Cache - с TTL и максимальным размером
Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
cache.put("key", "value");
String value = cache.getIfPresent("key");
// Caffeine - современная альтернатива Guava
com.github.benmanes.caffeine.cache.Cache<String, String> caffeineCache =
Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.recordStats()
.build();
// Spring Cache с аннотациями
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
return userService.findById(id);
}
7. Инструменты мониторинга
JVM метрики для мониторинга:
MemoryMXBean memoryMxBean = ManagementFactory.getMemoryMXBean();
MemoryUsage heapUsage = memoryMxBean.getHeapMemoryUsage();
System.out.println("Used: " + heapUsage.getUsed() / 1024 / 1024 + " MB");
System.out.println("Max: " + heapUsage.getMax() / 1024 / 1024 + " MB");
System.out.println("Committed: " + heapUsage.getCommitted() / 1024 / 1024 + " MB");
Prometheus метрики:
// Метрика размера кэша
Gauge cacheSize = Gauge.build()
.name("cache_size")
.help("Current cache size")
.labelNames("cache_name")
.register();
cacheSize.labels("user_cache").set(cache.size());
8. Чеклист диагностики
- Включи GC логи с флагами PrintGCDetails
- Анализируй паттерн GC: растёт ли heap после каждого Full GC?
- Создай heap dump перед перезагрузкой
- Открой в MAT/JProfiler и найди Top Consumers
- Проверь incoming references для подозрительных объектов
- Найди статические кэши в коде
- Проверь, есть ли TTL и max-size ограничения
- Используй правильные библиотеки: Guava Cache, Caffeine, Spring Cache