JVM и модель памяти
Java-код не исполняется процессором напрямую. javac компилирует исходники в переносимый байткод (.class), а отдельная для каждой платформы JVM загружает этот байткод, проверяет его на корректность, интерпретирует инструкцию за инструкцией и на горячих участках компилирует в нативный код через JIT. Именно поэтому один и тот же .class работает на Windows, Linux и macOS — переносим артефакт, а не машина под ним.
Вторая половина темы — как JVM управляет памятью во время работы. Объекты живут в общей для всех потоков куче (heap) и освобождаются автоматически сборкой мусора, а кадры методов с локальными переменными — в приватном для каждого потока стеке (stack), который сам растёт и сжимается на вызовах и возвратах. Понимание этого разделения превращает OutOfMemoryError, StackOverflowError и паузы GC из «магии» в предсказуемые следствия. Полная карта — в слоях ниже.
Карта темы
- JVM, JRE, JDK — что есть движок, что среда выполнения, что комплект разработчика; вложенность
JDK ⊃ JRE ⊃ JVM. - Платформенная независимость — «написал один раз — запускай везде»: переносимый байткод плюс своя JVM на каждой платформе.
- Байткод — что за формат лежит в
.class, как его прочитать черезjavap, и рольJIT. - Выполнение в JVM — путь класса: classloader → верификация → интерпретация/JIT.
- Области памяти — heap, стеки потоков, metaspace, PC-регистры; что общее, а что по потоку.
- Куча и стек — где живут объекты, где кадры, и почему одно собирает GC, а другое снимается pop.
- Сборка мусора — достижимость, поколения, компромисс пропускной способности и пауз.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
Считать, что для запуска .class нужен полный JDK | JDK нужен, чтобы собрать; запускает приложение JRE (JVM + библиотеки) |
| Называть байткод «нативным машинным кодом» | Байткод переносим; в нативный код его превращает JVM/JIT под конкретное железо |
| Думать, что JVM компилирует весь класс заранее, как компилятор C | JVM сперва интерпретирует, а JIT компилирует лишь горячие методы во время работы |
| Класть объекты в стек, а метаданные классов — в кучу | Объекты всегда в куче; метаданные классов — в method area (metaspace) |
Считать stack собираемым сборщиком мусора | Кадр снимается простым pop при возврате; GC работает только по куче |
| Полагать, что автоматическая память делает утечки невозможными | Достижимый, но ненужный объект (забытая ссылка) не будет собран — это утечка |
Ждать, что GC освободит объект в тот же миг, когда он стал недостижим | Время сборки недетерминировано — момент освобождения предсказать нельзя |
Значение для собеседований
Модель JVM спрашивают почти на любом Java-собеседовании — не как заучивание аббревиатур, а как проверку того, представляете ли вы, что происходит между вашим кодом и железом. Кандидат, который объясняет запуск через «classloader загружает .class, верификатор проверяет, дальше интерпретация плюс JIT на горячих методах», сразу отличается от «ну, Java просто выполняет байткод».
Что обычно проверяют:
- Разницу JVM / JRE / JDK и их вложенность.
- Почему переносим именно байткод, а JVM — платформозависима.
- Гибридное выполнение: интерпретация плюс JIT-компиляция горячих методов.
- Какие области памяти общие для потоков (heap, method area), а какие приватны (стеки, PC-регистры).
- Чем куча отличается от стека и почему
StackOverflowError— это не про кучу. - Что делает сборка мусора, чем платит за автоматизм и когда объект всё-таки утекает.
Типичный неверный ответ: «JVM компилирует Java в машинный код, как C». Это повод обсудить, что javac порождает переносимый байткод, а не нативный код, что каждую платформу обслуживает своя JVM, и что нативным код становится лишь для горячих методов через JIT уже во время работы.