С чего начать разбираться при проблемах с OutOfMemoryError
Что проверяют
Есть ли у вас порядок действий, а не рефлекс «поднимем -Xmx». Интервьюер хочет услышать, что сначала читают текст ошибки, потом снимают дамп и ищут, кто держит память. Следом обычно спрашивают, как отличить утечку от просто недостаточного размера heap.
Как отвечать
Шаг первый: текст после java.lang.OutOfMemoryError. Он сразу сужает поиск:
- Java heap space: не хватило кучи, самый частый случай;
- GC overhead limit exceeded: сборщик почти всё время чистит и почти ничего не освобождает, по сути та же нехватка heap;
- Metaspace: слишком много загруженных классов, типичная причина это утечка загрузчиков классов при горячем редеплое или генерация прокси;
- unable to create native thread: упёрлись в лимит потоков ОС или в память вне кучи, heap тут ни при чём;
- Direct buffer memory: кончился лимит off-heap буферов NIO, часто встречается с Netty.
Шаг второй: данные. На проде заранее включают -XX:+HeapDumpOnOutOfMemoryError и -XX:HeapDumpPath, чтобы дамп появился в момент падения. С живого процесса дамп снимают jcmd <pid> GC.heap_dump, быстрый обзор даёт jcmd <pid> GC.class_histogram. Логи GC (-Xlog:gc*) показывают картину: если после каждой полной сборки занятая память растёт, это утечка, если держится ровно на пике нагрузки, heap просто мал.
Шаг третий: анализ дампа в Eclipse MAT: отчёт Leak Suspects, дерево доминаторов, retained size. Обычно виноваты статическая коллекция без очистки, кеш без лимита и вытеснения, ThreadLocal в пуле потоков, незакрытые ресурсы.
Где спотыкаются
Сразу увеличивают -Xmx. При утечке это лишь отодвигает падение, а дамп потом снимать и разбирать дольше.
Путают OOM внутри JVM с убийством контейнера. Если под завершился с кодом 137 и статусом OOMKilled, исключения не было: процесс превысил лимит cgroup, и смотреть нужно на -XX:MaxRAMPercentage и память вне heap.
Называют PermGen актуальным. Его убрали в Java 8, место заняла Metaspace, и совет крутить -XX:MaxPermSize выдаёт устаревшие знания.