Ответ
Анализ следует начать со сбора и изучения дампа памяти (heap dump), созданного в момент ошибки OOME.
План действий:
- Включите автоматическое создание дампа при OOME, добавив в аргументы JVM:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumps - Проанализируйте дамп с помощью профилировщиков:
- Eclipse Memory Analyzer (MAT) — мощный инструмент для поиска утечек.
- VisualVM или JProfiler — для интерактивного анализа. В них нужно:
- Найти объекты, занимающие наибольший объем памяти (вкладка Histogram или Biggest Objects).
- Построить путь до GC Root для этих объектов, чтобы понять, что удерживает их в памяти.
- Искать паттерны утечек, например, коллекции, которые бесконечно растут.
- Изучите логи сборки мусора (GC logs), включив их:
-Xlog:gc*:file=gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10MОбратите внимание на частоту и длительность сборок мусора, особенно Full GC.
Пример кода с потенциальной утечкой (статическая коллекция):
public class LeakyCache {
private static final Map<String, byte[]> CACHE = new HashMap<>();
public void storeData(String id, byte[] data) {
// Данные добавляются, но никогда не удаляются -> утечка
CACHE.put(id, data);
}
}
Дополнительные шаги: проверка настроек памяти (-Xmx, -Xms), мониторинг метрик (через JMX/Prometheus) и анализ кода на наличие подобных паттернов.
Ответ 18+ 🔞
Давай разберём эту хрень с OOME, а то уже глаза на лоб лезут. Слушай, первым делом — надо поймать эту суку на месте преступления. Не просто «ой, память кончилась», а снять отпечатки пальцев, блядь.
Что делаем, чтобы не остаться с носом:
-
Заставляем JVM стучать на саму себя. Прикручиваем к запуску приложения волшебные ключи, чтобы при каждом таком пиздеце она автоматически вываливала дамп памяти, как пьяный свидетель в участке:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/куда/ты/хочешь/эти/дампыТеперь при OOME в указанной папке появится файлик
.hprof— это и есть полный слепок памяти в момент аварии. Золотая жила, ёпта! -
Начинаем следствие с профилировщиков. Берём этот дамп и засовываем в специальные штуки:
- Eclipse Memory Analyzer (MAT) — это, блядь, как Шерлок Холмс для утечек памяти. Самый мощный.
- VisualVM или JProfiler — тоже ничего, для первичного осмотра сгодится.
Внутри этих штук ищем не просто кто большой, а кто виноватый:
- Открываем вкладку Histogram (гистограмма) или Biggest Objects. Смотрим, какие объекты занимают овердохуища места. Массивы байтов? Хэш-мапы размером с Казахстан?
- Для главных подозреваемых строим путь до GC Root (это такая функция «кто его держит?»). Это покажет, какая сволочь (чаще всего статическое поле или живая тред-пула) не отпускает эти объекты, мешая сборщику мусора их подмести.
- Ищем паттерны-убийцы. Бесконечно растущая коллекция — классика жанра. Как в этом примере, который я сейчас высеку:
public class LeakyCache {
// Вот она, падла! Статическая. Живёт вечно. Всё, что в неё попало, навсегда.
private static final Map<String, byte[]> CACHE = new HashMap<>();
public void storeData(String id, byte[] data) {
// Данные закидываются, а удалять их никто не собирается. Прямой путь к OOME.
CACHE.put(id, data); // С каждым вызовом могила копается глубже.
}
}
- Смотрим, как работал дворник (Garbage Collector). Включаем логирование его подвигов, чтобы понять, не захлёбывался ли он:
-Xlog:gc*:file=gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10MКачаем логи и ищем в них признаки беды: участившиеся Full GC, которые длятся годами, но почти не освобождают памяти. Это верный знак, что память забита жирными, привязанными объектами.
Ну и по мелочи: глянь, не поставили ли вы -Xmx на 10 мегабайт для базы данных в 100 гигов. Посмотрите метрики (JMX, Prometheus) — как память росла перед падением. И просто пройдитесь по коду, выискивая вот такие же «кэши», статические коллекции или незакрытые ресурсы. Удачи, сыщик!