Сначала ответь вслух
Скажи ответ вслух хотя бы пару минут, потом открой эталон и сравни. Эталон открыт всем и не требует входа.
Показать ответ
Эталонный ответ
Сборщик мусора ищет объекты, до которых нельзя дойти от корней: стеков потоков, статических полей, JNI-ссылок. Всё достижимое он обязан сохранить, даже если программе это больше не нужно.
- Статическая карта-кэш без ограничения размера и вытеснения.
- Слушатели и подписки, которые добавили и не сняли.
- ThreadLocal в потоках пула без
remove(): поток живёт долго, значение вместе с ним. - Изменяемые ключи в HashMap: запись нельзя найти и удалить обычным способом.
- Загрузчики классов, которые держит ссылка после повторного развёртывания.
Диагностика: снять дамп кучи (jcmd <pid> GC.heap_dump или -XX:+HeapDumpOnOutOfMemoryError), открыть в анализаторе и смотреть дерево доминаторов: какой объект удерживает больше всего памяти.
Как у тебя получилось?
Уточняющие вопросы
Так обычно копают глубже. Раскрой строку, когда ответишь вслух.
Уточнение 1-го уровня: Почему ThreadLocal в пуле потоков течёт?
Ответ уровня Middle
Значение хранится в карте самого потока. Ключ там — слабая ссылка на ThreadLocal, а значение — сильная. Поток пула не умирает, и значение живёт, пока его не удалят через remove() в finally.
Уточнение 2-го уровня: Как отличить утечку от нормального роста кучи?
Ответ уровня Senior
По журналу сборки: если после каждой полной сборки занятая старым поколением память растёт, что-то удерживает объекты. Два дампа с интервалом покажут, что именно растёт.
Типичные ошибки
- Увеличивать -Xmx вместо поиска того, что удерживает память.
- Не очищать ThreadLocal в веб-приложениях.
Первоисточники
Быстрые задания по теме
Для темы «Сборка мусора и утечки» быстрых заданий пока нет
Можно проверить всю зону «JVM и память» тестом на 10 минут.
Проверить себя за 10 минут