Память JVM и сборка мусора на собеседовании
Спрашивают не алгоритм сборщика, а границы областей памяти. Разбираем, что лежит в куче, что в стеке и откуда берётся OutOfMemoryError: Metaspace.
Схемы памяти JVM в разных статьях выглядят по-разному, и это сбивает сильнее, чем сама тема: где-то нарисованы Eden и два Survivor, где-то PermGen, где-то метапространство. Понять по картинкам, какая схема верна, невозможно: верны почти все, просто они про разные вещи. Ниже — граница, которая всё расставляет по местам, и вопросы, которые из неё выводятся.
Всё, что сказано о поведении, относится к Java SE 21 и виртуальной машине HotSpot там, где это оговорено отдельно.
Что на поток, а что общее §
Спецификация виртуальной машины делит области памяти по одному признаку, и он объясняет почти всё остальное: одни области принадлежат потоку, другие общие для всех потоков.
На поток:
- pc-регистр — у каждого потока свой; он хранит адрес выполняемой инструкции;
- стек виртуальной машины — приватный для потока и создаётся вместе с ним; хранит фреймы, а в них — локальные переменные и промежуточные результаты;
- стек нативных методов, если реализация его поддерживает, — обычно тоже создаётся на каждый поток.
Общие для всех потоков:
- куча — создаётся при старте машины; из неё выделяется память для всех экземпляров классов и массивов, и её память освобождает сборщик мусора. Спецификация формулирует это жёстко: объекты никогда не освобождаются явно;
- метод-область — тоже создаётся при старте и хранит структуры уровня класса: пул констант времени выполнения, данные полей и методов, код методов и конструкторов.
Из этой границы сразу выводится ответ на вопрос о том, где живут объект и ссылка на него. Ссылка, то есть локальная переменная, лежит во фрейме на стеке потока; сам объект — в общей куче. Поэтому два потока не видят локальных переменных друг друга, но прекрасно видят один и тот же объект, если оба получили на него ссылку.
Почему OutOfMemoryError бывает разный §
OutOfMemoryError воспринимается как одна ошибка с одним лечением: добавить памяти. Но спецификация предписывает эту ошибку в четырёх разных местах, и причины у них разные.
- Куча. Если вычислению нужно больше кучи, чем может предоставить система автоматического управления памятью.
- Стек виртуальной машины. Если стек можно расширять динамически, но памяти на расширение нет, или если её не хватает, чтобы создать начальный стек для нового потока.
- Метод-область. Если память в ней не может быть выделена под запрос.
- Стек нативных методов. Те же два условия, что у стека виртуальной машины.
Отсюда вывод, ради которого стоит держать этот список в голове: увеличение кучи лечит ровно один случай из четырёх. Стеки потоков и метод-область — отдельные области, память под них выделяется отдельно от кучи, и её размер на них не влияет.
Отдельно стоит различать StackOverflowError и OutOfMemoryError на стеке: имя ошибки разное, область одна и та же. Первая означает, что потоку нужен стек больше разрешённого: рекурсия ушла слишком глубоко. Вторая — что памяти не хватает, чтобы стек создать или расширить. Первая про глубину вычисления, вторая про наличие памяти в системе.
Почему в разных статьях разные схемы памяти §
Здесь и находится источник путаницы, с которой начинается тема. Спецификация задаёт, какие области существуют, и почти не задаёт, как они устроены внутри. Про кучу сказано, что она общая, создаётся при старте, из неё выделяются объекты и массивы, а освобождает её сборщик. Про то, что она разделена на поколения, не сказано ничего.
Значит, всё привычное устройство (молодое поколение, Eden, области выживших, старое поколение, продвижение объектов между ними) — свойство конкретной реализации, а не общее правило платформы. То же относится к метапространству: в спецификации такого термина нет, там метод-область, а метапространство — то, как её реализует HotSpot.
Отсюда практическое следствие для ответа: различие стоит проговаривать вслух. Сравните два ответа. Первый: «в куче есть Eden и два Survivor». Второй: «спецификация делит память на области и не описывает устройство кучи; в HotSpot куча поколенческая — Eden, области выживших, старое поколение». Второй ровно на одну фразу длиннее и заметно точнее.
Тем же различением объясняется и путаница с PermGen: это была реализация метод-области в старых версиях HotSpot, которую заменило метапространство. Спецификация при этом не менялась; менялась реализация, а вместе с ней и схемы в статьях разных лет.
Что можно сказать про сборщики точно §
Перечисление сборщиков — самая доступная часть темы и потому наименее содержательная: список есть в любом справочнике. Полезнее знать несколько вещей, подтверждённых источником.
Что гарантирует платформа. Ровно одно: память кучи освобождается автоматически, и объекты никогда не освобождаются явно. Всё остальное (как именно, когда и с какими паузами) — вопрос реализации и настройки.
Что сказано про выбор по умолчанию. Руководство по настройке сборки мусора формулирует это через форму приложения: на серверных машинах, где большой объём памяти и два и более процессора, по умолчанию выбирается сборщик G1. Для небольших приложений, которым хватает кучи примерно до ста мегабайт, руководство называет достаточным последовательный сборщик, если специальное поведение других не требуется.
Формулировка «по умолчанию используется G1» без оговорки про класс машины неточна. Лучше воспроизводить условие: выбор зависит от того, на чём приложение запущено.
Чего не стоит утверждать. Сравнение сборщиков по паузам и пропускной способности — то место, где легче всего сказать лишнее. Разумнее назвать сам факт размена: сборщик решает задачу компромиссом между временем работы приложения, длительностью пауз и расходом памяти, и разные сборщики выбирают разные точки этого компромисса. Конкретные числа зависят от версии, размера кучи и профиля нагрузки, и называть их по памяти не стоит.
Стоит быть готовым к тому, что тема не заканчивается на себе. В опубликованном разборе одного собеседования вопросы про виды сборщиков и типы ссылок идут подряд, и сразу за ними — блок про конкурентные классы и взаимные блокировки. Разбор про многопоточность — соседний.
Проверьте себя
Сначала проговорите ответ, потом сверьтесь с текстом.
Какие области памяти есть в JVM?
Что проверяют: перечисляете вы области или называете признак, по которому они делятся. Признак позволяет вывести следствия; перечисление — нет.
Частая ошибка: назвать «стек и куча» и остановиться. Метод-область в этот ответ не попадает, а с ней связана часть вопросов про загрузку классов и про ошибки памяти.
Приложение падает с OutOfMemoryError. Что вы будете делать?
Что проверяют: различаете вы разные состояния с одинаковым именем ошибки или предлагаете одно универсальное действие.
Частая ошибка: сразу ответить «увеличу кучу». Ответ верен для одного случая из четырёх, а начинать разумно с того, какая именно область исчерпана: это написано в самом сообщении об ошибке.
Из чего состоит куча?
Что проверяют: знаете вы, чьё это утверждение. Устройство кучи — свойство реализации, и точный ответ это оговаривает.
Частая ошибка: назвать поколения как часть спецификации. Спецификация про внутреннее устройство кучи молчит.
Какой сборщик мусора используется по умолчанию?
Что проверяют: воспроизводите вы условие выбора или один заученный ответ. У вопроса есть условие, и оно входит в правильный ответ.
Частая ошибка: назвать сборщик без оговорки про класс машины. Выбор по умолчанию описан именно как зависящий от объёма памяти и числа процессоров.
Проверьте эти темы вслух
В тренажёре вы отвечаете голосом, а разбор показывает, где ответ просел.