JVM и память
JVM/JRE/JDK, платформонезависимость, выполнение байт-кода, области памяти и сборка мусора.
9 вопросов
MiddleТеорияОчень частоВ чём разница между heap и stack в памяти JVM?
В чём разница между heap и stack в памяти JVM?
heap хранит объекты, созданные через new, общий для всех потоков и очищается сборкой мусора. stack хранит кадры методов — локальные переменные и операнды каждого кадра, — приватен для одного потока и работает по принципу LIFO. Кадр кладётся при вызове и снимается при возврате метода, освобождая локальные переменные автоматически, без участия GC.
Типичные ошибки
- ✗Менять их роли — говорить, что объекты живут в
stack, а кадры вheap - ✗Утверждать, что
stackсобирается мусорщиком, хотя кадры снимаются простым pop при возврате - ✗Забывать, что
stackпо потоку, аheapобщий для всех потоков
Уточняющие вопросы
- →Где на самом деле хранится ссылка, которую локальная переменная держит на объект в
heap? - →Почему глубокая рекурсия выбрасывает
StackOverflowError, а неOutOfMemoryError?
MiddleТеорияОчень частоКакие основные области памяти времени выполнения есть у JVM?
Какие основные области памяти времени выполнения есть у JVM?
heap общий для всех потоков и хранит все объекты. У каждого потока свой stack из кадров с локальными переменными и промежуточными результатами. Method area (metaspace) хранит метаданные классов, static-поля и пул констант времени выполнения. PC-регистры по потоку отслеживают текущую инструкцию, а native method stacks обслуживают вызовы в не-Java код. Heap и method area общие, а стеки и PC-регистры — по потоку.
Типичные ошибки
- ✗Путать, какие области общие для потоков (
heap, method area), а какие по потоку (стеки) - ✗Забывать про method area/metaspace и класть метаданные классов в
heapвместе с объектами - ✗Считать, что объекты могут жить в
stack, а не всегда вheap
Уточняющие вопросы
- →Почему permanent generation заменили на metaspace, и где располагается metaspace?
- →Какая область выбрасывает
StackOverflowError, а какаяOutOfMemoryError, и почему?
JuniorТеорияЧастоВ чём разница между JVM, JRE и JDK и как они соотносятся?
В чём разница между JVM, JRE и JDK и как они соотносятся?
JVM — это движок, который загружает и выполняет Java bytecode на конкретной платформе. JRE объединяет JVM и стандартные библиотеки, нужные, чтобы запускать приложение. JDK — это JRE плюс инструменты разработки: компилятор javac, отладчик и jar, нужные, чтобы собрать приложение. Они вложены: JDK ⊃ JRE ⊃ JVM.
Типичные ошибки
- ✗Думать, что JRE включает
javac, хотя компилятор поставляется только в JDK - ✗Считать, что для запуска уже скомпилированного приложения нужен полный JDK
- ✗Путать JVM (движок выполнения) со всем набором инструментов JDK
Уточняющие вопросы
- →Раз современные JDK содержат всё, зачем вообще выделять отдельный JRE?
- →Что добавляет
JIT-компилятор JVM сверх простого выполнения?
MiddleТеорияЧастоКак JVM выполняет скомпилированный класс, шаг за шагом?
Как JVM выполняет скомпилированный класс, шаг за шагом?
Classloader загружает .class-файл в память. Затем верификатор bytecode проверяет, что он корректен и типобезопасен. Execution engine выполняет его: интерпретирует bytecode инструкция за инструкцией, а JIT-компилятор (just-in-time) отслеживает горячие методы и компилирует их в нативный код ради скорости. Значит, выполнение гибридное — сначала интерпретация, затем JIT-оптимизация там, где это окупается.
Типичные ошибки
- ✗Думать, что JVM заранее компилирует весь класс в нативный код, как компилятор C
- ✗Считать, что
JIT— это просто интерпретатор, а не компилятор горячих методов в нативный код - ✗Пропускать шаг верификации, считая любой
.classзаведомо типобезопасным
Уточняющие вопросы
- →Как JVM решает, что метод достаточно «горячий», чтобы скомпилировать его
JIT? - →Какой некорректный
bytecodeверификатор отклоняет до выполнения?
JuniorТеорияИногдаКак Java достигает платформонезависимости «написал один раз — запускай везде»?
Как Java достигает платформонезависимости «написал один раз — запускай везде»?
javac компилирует исходный код в платформонезависимый bytecode, а не в нативный машинный код. Этот bytecode одинаков на любой операционной системе. Чтобы выполнить его, на каждой платформе поставляется своя JVM, которая читает переносимый bytecode и исполняет его для этого железа. Значит, скомпилированный артефакт переносим, а JVM под ним — платформозависима.
Типичные ошибки
- ✗Называть скомпилированный
bytecodeнативным машинным кодом, а не переносимой промежуточной формой - ✗Считать, что сама JVM одинакова и переносима между платформами
- ✗Думать, что везде запускаются исходные
.java-файлы, а неbytecode
Уточняющие вопросы
- →Если
bytecodeпереносим, почему JVM нужно скачивать отдельно под каждую операционную систему? - →Как
JIT-компилятор сочетает переносимость с выполнением на нативной скорости?
MiddleТеорияИногдаЧто такое загрузчики классов bootstrap, platform и application и как они взаимодействуют?
Что такое загрузчики классов bootstrap, platform и application и как они взаимодействуют?
Это три встроенных загрузчика классов JVM, выстроенных в цепочку родителей. Загрузчик bootstrap нативный и грузит базовые классы JDK (java.base); platform грузит остальные модули JDK; application грузит ваши классы с classpath. Работают они по делегированию родителю: загрузчик сперва спрашивает родителя и определяет класс сам, только если родитель его не нашёл. Именно это не даёт java.lang.String с classpath подменить настоящий.
Типичные ошибки
- ✗Считать, что загрузчик application опрашивают раньше его родителей
- ✗Полагать, что идентичность класса — только его имя, игнорируя загрузчик, который его определил
- ✗Думать, что все классы грузятся энергично на старте, а не при первом обращении
Уточняющие вопросы
- →Почему два класса с одинаковым полным именем могут быть разными типами в рантайме?
- →Что заменил загрузчик platform, когда JDK стал модульным?
MiddleТеорияИногдаЧем metaspace отличается от PermGen, который он заменил в Java 8?
Чем metaspace отличается от PermGen, который он заменил в Java 8?
PermGen был областью фиксированного размера внутри heap, хранившей метаданные классов; при переполнении вы получали OutOfMemoryError: PermGen space и шли крутить -XX:MaxPermSize. В Java 8 его заменил metaspace, который живёт в нативной памяти вне heap и растёт по требованию, ограниченный только -XX:MaxMetaspaceSize (по умолчанию без предела). Метаданные класса освобождаются при выгрузке его загрузчика, поэтому утечка загрузки классов теперь проявляется ростом нативной памяти, а не heap.
Типичные ошибки
- ✗Считать metaspace областью heap, ограниченной
-Xmx - ✗Ожидать, что metaspace по умолчанию фиксированного размера, как был PermGen
- ✗Полагать, что метаданные класса не освобождаются никогда после его загрузки
Уточняющие вопросы
- →О чём обычно говорит неуклонно растущий metaspace применительно к загрузчикам классов?
- →Почему
-XX:MaxMetaspaceSizeвсё равно важен, если metaspace растёт по требованию?
MiddleТеорияРедкоЧто решает модульная система платформы Java JPMS, чего не мог classpath?
Что решает модульная система платформы Java JPMS, чего не мог classpath?
Файл module-info.java объявляет имя модуля, что он requires и какие пакеты exports. Это даёт строгую инкапсуляцию — неэкспортированный пакет недостижим даже через рефлексию — и надёжную конфигурацию: пропавший или задвоенный модуль падает на старте, а не превращается в NoClassDefFoundError посреди работы. Classpath был плоским мешком jar-ов, зависящим от порядка и без публичного API. Module path читает jar-ы как модули; код, оставшийся на classpath, попадает в unnamed module и сохраняет прежнее поведение.
Типичные ошибки
- ✗Принимать
module-info.javaза манифест зависимостей вродеpom.xmlв Maven - ✗Ожидать, что рефлексия доберётся до пакета, который модуль не
exports - ✗Считать, что classpath исчез, а не выжил в виде unnamed module
Уточняющие вопросы
- →Что такое unnamed module и что получает от него jar, оставшийся на classpath?
- →Чем
opensотличается отexports, когда фреймворку нужен рефлексивный доступ?
SeniorДизайнРедкоВы выбираете базовый JDK для нового backend-сервиса, который проживёт годы, и решение нужно принять до первого релиза. Остальная платформа работает на Java 17; платформенная команда поддерживает любую LTS-линию. Сервис нагружен I/O — тысячи одновременных запросов веером уходят во внешние HTTP API — и команда рассчитывает опираться на virtual threads и конвейеры Stream. Java 21 была выбором по умолчанию с самого релиза; Java 25 — новейшая LTS-линия. Двое инженеров хотят остаться на 21, доказывая, что настоящий приз — structured concurrency, отмена всего веера как единого целого, и что его стоит подождать. На какой линии вы выстроите базу и почему? Назовите, что Java 25 действительно финализирует по сравнению с 21, что там всё ещё preview и что удержало бы вас на 21.
Вы выбираете базовый JDK для нового backend-сервиса, который проживёт годы, и решение нужно принять до первого релиза. Остальная платформа работает на Java 17; платформенная команда поддерживает любую LTS-линию. Сервис нагружен I/O — тысячи одновременных запросов веером уходят во внешние HTTP API — и команда рассчитывает опираться на virtual threads и конвейеры Stream. Java 21 была выбором по умолчанию с самого релиза; Java 25 — новейшая LTS-линия. Двое инженеров хотят остаться на 21, доказывая, что настоящий приз — structured concurrency, отмена всего веера как единого целого, и что его стоит подождать. На какой линии вы выстроите базу и почему? Назовите, что Java 25 действительно финализирует по сравнению с 21, что там всё ещё preview и что удержало бы вас на 21.
Стройте базу на Java 25: это текущая LTS-линия, так что ничего из данного 21 не теряется. По сравнению с 21 она финализирует Scoped Values (JEP 506) и наследует от Java 24 исправление пиннинга virtual threads (JEP 491 — synchronized больше не прибивает несущий поток) и финальные Stream Gatherers (JEP 485), на которые ваш нагруженный I/O сервис и опирается. Structured concurrency в 25 всё ещё preview, поэтому это не повод для перехода. Остаться на 21 стоит, только если критичная зависимость, агент или JDK вендора не сертифицированы под 25.
Типичные ошибки
- ✗Принимать Java 25 за короткоживущий feature-релиз, а не за текущую LTS-линию
- ✗Считать structured concurrency финальным в 25 и строить вокруг него дизайн
- ✗Полагать, что virtual threads ведут себя на 21 и 25 одинаково, включая пиннинг
Уточняющие вопросы
- →Почему исправление пиннинга на
synchronizedменяет то, какая доля кода может жить на virtual threads? - →Чего стоят preview-API вроде structured concurrency в флагах сборки и рантайма?