Сегодня Java исполняется 30. Три десятилетия — это эпоха. За это время технологии успели смениться кардинально, а некоторые модные языки — появиться, поиграть мускулами и тихо исчезнуть. А Java по-прежнему тут. Она не просто "ещё жива" — она стабильно занимает лидирующие места в рейтингах, используется в крупнейших корпорациях и продолжает эволюционировать. Как так получилось?

В этой статье я расскажу, почему Java прожила 30 лет и всё ещё чувствует себя уверенно: как она развивалась, как решаются проблемы производительности JVM, и почему на неё до сих пор делают ставку разработчики, архитекторы и CTO.

Введение

Мы не будем начинать с Sun, Green Team, Oak и Java Father. Можем считать, что отправной точкой в наших воспоминаниях о тех былых временах может быть описание языка Java, данное Джеймсом Айри в его статье "A Brief, Incomplete, and Mostly Wrong History of Programming Languages":

1996 — Джеймс Гослинг изобретает Java. Java — сравнительно многословный язык программирования с автоматическим управлением памятью, основанный на классах, со статической типизацией, одиночной диспетчеризацией и объектно-ориентированным подходом. Он поддерживает одиночное наследование реализации и множественное наследование интерфейсов. Sun помпезно восхваляет Java за новизну.

Зачем вообще обсуждать язык, которому больше четверти века? Ведь вы просто пишете в стиле Java 8 каждый день — и всё работает. Код компилируется, баги дебажутся, GC собирает мусор.

Но дело в том, что Java — это не архаика. Это живая и активно развивающаяся платформа. За последние 10 лет язык и его экосистема эволюционировали гораздо быстрее, чем многие готовы признать. Мы уже давно не живём в мире Java 8, где казалось, что язык застыл. Сегодня каждые шесть месяцев выходит новая версия, и с ней — новые возможности, улучшения, а иногда и новые парадигмы мышления.

Java не просто выжила, она задала планку обратной совместимости и аккуратности в выпуске новых функций — это уникальное инженерное достижение.

Чтобы понять, как язык оставался релевантным три десятилетия — и почему его развитие сегодня вызывает не меньше интереса, чем в 1996 году — нужно начать с того, как именно Java развивается: кто принимает решения, откуда берутся фичи и почему каждая новая версия — это не лотерея, а результат выверенного процесса.

С этого и начнём.

Как сегодня развивается Java: релизный цикл, open source и множество поставщиков

Многие по-прежнему воспринимают Java как монолитный продукт от Oracle, но в действительности разработка языка и платформы уже давно ведётся открыто и при участии большого количества организаций и индивидуальных контрибьюторов.

Java не всегда была открытой. Это изменилось в 2006 году, когда на конференции JavaOne компания Sun Microsystems объявила о переходе Java на открытый исходный код. Код платформы был опубликован под лицензией GPLv2 с дополнительными исключениями. Первой полностью open source-версией стала Java 7 — начиная с неё, основная работа над Java сосредоточена в проекте OpenJDK.

Процессы OpenJDK максимально прозрачны. Большая часть обсуждений ведётся в открытых mailing листах: есть как постоянные (например, core-libs-dev для стандартных библиотек), так и временные, создаваемые под конкретные инициативы (например, valhalla-dev или amber-dev). Именно там обсуждаются предложения, дорабатываются идеи и формируются JEP'ы — Java Enhancement Proposals, описания новых фич языка.

До Java 9 новые релизы выходили «по готовности»: основной фичей релиза могла стать поддержка лямбд (Java 8) или система модулей (Java 9). Это делало релиз непредсказуемым по срокам, зато фокусированным.

С выходом Java 10 ситуация изменилась. Oracle и сообщество приняли решение перейти к фиксированному циклу релизов: новые версии Java теперь выходят каждые шесть месяцев, в марте и сентябре. Эта модель:

  • не откладывает релиз, если какая-то фича не готова — её просто переносят на следующий релиз;

  • позволяет быстрее доставлять улучшения;

  • делает основную ветку разработки всегда готовой к выпуску;

  • поощряет разработку фич в изолированных ветках, с последующим мёрджем после стабилизации.

На практике, далеко не все версии Java используются в продакшене. Разработчики чаще всего ориентируются на LTS-релизы — версии с долгосрочной поддержкой (Long-Term Support), которые получают обновления и патчи в течение нескольких лет. К таким версиям относятся:

  • Java 8 (2014, задним числом признана LTS)

  • Java 11 (2018)

  • Java 17 (2021)

  • Java 21 (2023)

  • Java 25 (2025 — следующая LTS)

Обычные (non-LTS) версии интересны в первую очередь энтузиастам, early-adopters и авторам библиотек. Большие команды и особенно корпоративные клиенты предпочитают мигрировать от одной LTS к другой, с большими интервалами — это снижает риски и позволяет заранее планировать переходы.

После перехода к open source появилась важная особенность: Oracle — не единственный поставщик JDK. Помимо Oracle JDK, существуют альтернативные бинарные сборки от:

  • Eclipse Temurin (ранее Eclipse Adoptium),

  • BellSoft (Liberica),

  • Amazon (Corretto),

  • Azul (Zulu),

  • Red Hat, SAP, Microsoft,

  • Российский дистрибутив Axiom JDK

Многие из них предоставляют полностью бесплатные и свободно распространяемые версии JDK. Интересен тот факт, что начиная с Java 11, двоичные файлы от Oracle перестали быть свободно доступными для продакшена без подписки на поддержку, но с 2021 года на основании лицензии NFTC (Oracle No-Fee Terms and Conditions) Java 21 и более позднии версии бесплатны для всех пользователей — даже для коммерческого и производственного использования.

Для LTS-релизов существует механизм так называемых проектов обновлений. В рамках этих проектов сообщество и компании продолжают поддерживать старые версии, публикуя регулярные обновления безопасности, багфиксы и, в редких случаях, бэкпорты новых возможностей (например, Java Flight Recorder в Java 8u272).

Однако набор допустимых изменений строго ограничен: ничего, что может нарушить обратную совместимость. Это сознательная политика, позволяющая командам использовать LTS-релизы как максимально стабильную и предсказуемую платформу.

История версий Java

Прежде чем перейти к продвинутым возможностям современных версий Java, оглянемся назад и проследим, как язык развивался с момента своего первого релиза. История версий помогает не только расширить кругозор, но и понять, почему Java устроена именно так, как она устроена. И как с каждым релизом она становилась всё более зрелой платформой.

Java от Sun Microsystems


JDK 1.0 (1996)

Первая стабильная версия Java от Sun Microsystems вышла под названием JDK 1.0.2. В те времена JDK представлял собой архив с набором инструментов: компилятор, интерпретатор, генератор документации, архиватор и библиотека классов. Всё, что нужно для написания и запуска Java-программ.

С этого момента Java стала платформой: с собственным рантаймом (JRE), идеологией "write once, run anywhere" и стремлением к переносимости.


JDK 1.2 (1998): Playground и Java 2

Java 1.2 стала настолько масштабным обновлением, что с неё началась эра Java 2. Появилось деление платформы на три редакции:

  • J2SE — стандартная редакция (для десктопа и серверов),

  • J2EE — корпоративная (веб, распределённые системы),

  • J2ME— для мобильных и встроенных устройств.

Ключевые нововведения:

  • JIT-компилятор стал частью JVM — ускорился запуск и выполнение кода;

  • Swing API — новая библиотека для создания GUI-приложений, на смену AWT;

  • Java Collections Framework — списки, множества, карты и связанная инфраструктура.


J2SE 1.3 (2000): Kestrel

Версия с кодовым именем Kestrel добавила поддержку XML API и Java Sound. Хотя это был скорее "полировочный" релиз, он закладывал основы для будущих фич.


J2SE 1.4 (2002): Merlin и Java Community Process

Java 1.4 стала первой версией, в которой активно участвовало сообщество через Java Community Process (JCP). Это сделало релиз более структурированным. Ключевые фичи:

  • Поддержка IPv6;

  • Неблокирующий ввод-вывод (NIO);

  • Logging API — впервые появилась встроенная система логирования;

  • API для обработки изображений.

Также именно в это время появился знаменитый логотип с чашкой кофе, который теперь неизменно ассоциируется с Java.


J2SE 5.0 (2004): Tiger

Хотя формально это была версия 1.5, она была представлена как Java 5.0 — из-за огромного количества важных нововведений. Это был момент, когда Java сделала качественный скачок в зрелость:

  • Generics — обобщения и статическая типизация коллекций;

  • Annotations — аннотации как метаданные в коде;

  • Autoboxing / Unboxing — автоматическое преобразование между примитивами и обёртками;

  • Enum — полноценные перечисления;

  • Varargs — аргументы переменной длины;

  • Enhanced for-each loop — удобный цикл по коллекциям;

  • Static imports — импорт статических методов и констант;

  • Улучшения в Swing, RMI, и появление класса Scanner.

Интересный факт: это была первая версия Java, официально доступная на macOS.


JDK 6 (2006): Mustang и финальный релиз Sun

Последний крупный релиз Java, выпущенный Sun Microsystems до покупки компанией Oracle. Несмотря на задержку, в версию вошло множество улучшений:

  • Существенное повышение производительности платформы;

  • Улучшения для веб-сервисов и JDBC 4.0;

  • Расширения GUI-интерфейсов (включая SwingWorker, сортировку таблиц, двойную буферизацию и др.).

В конце 2008 года вышла и первая версия JavaFX, как попытка создать новую платформу для GUI-приложений. На тот момент JavaFX представлял собой отдельный скриптовый язык, и его будущее было неопределённым: Swing ещё доминировал, а внутри Sun не было единого видения, стоит ли JavaFX вообще развивать.

Java от Oracle

После довольно турбулентного периода в конце 2000-х годов, судьба Java снова изменилась. В 2009 году компания Oracle приобрела Sun Microsystems за $5.6 млрд. Это было громкое и неоднозначное событие: многие ключевые инженеры, включая самого Джеймса Гослинга, покинули компанию, и сообщество с тревогой следило за тем, в каком направлении Oracle поведёт платформу.


Java 7 (2011)

Переход от Sun к Oracle занял около пяти лет, и только в 2011 году вышел Java 7 (кодовое имя — Dolphin). Это был первый релиз под эгидой Oracle и результат сотрудничества с сообществом OpenJDK и Java Community Process (JCP).

Хотя ожидания были высоки, в релиз вошло меньше новшеств, чем хотелось бы — проекты Lambda и Jigsaw были отложены. Но в релиз всё же попали важные изменения:

  • invokedynamic — поддержка динамических языков (Groovy, JRuby и др.) на JVM;

  • Сжатые 64-битные указатели — экономия памяти в JVM;

  • Изменения в языке в рамках проекта Coin:

    • String в switch;

    • try-with-resources;

    • diamond-оператор (<>) для generics;

    • бинарные литералы;

    • множественная обработка исключений;

  • Новая библиотека java.nio.file;

  • Улучшения в многопоточности и сортировка Timsort.

Также был подтверждён курс на JavaFX: версия 2.0 отказалась от скриптового DSL и стала полноценным Java API для UI с аппаратным ускорением (движок Prism). Постепенно JavaFX начал вытеснять Swing в новых проектах.

Ещё один важный момент: именно с Java 7 проект OpenJDK стал официальной эталонной реализацией Java SE. Многие ожидали, что Oracle ужесточит лицензирование, но, напротив, компания активно поддержала open-source направление.


Java 8 (2014)

Релиз, который навсегда изменил Java. Java 8 (кодовое имя — Spider) была в разработке три года и включила в себя всё то, что не успели сделать в Java 7. Это был большой и долгожданный апдейт:

  • Лямбда-выражения — функциональный стиль программирования в Java;

  • Default-методы в интерфейсах;

  • Новый Date & Time API (java.time.*);

  • Stream API — декларативная обработка коллекций и поддержка параллелизма;

  • Улучшенная интеграция с JavaScript (движок Nashorn);

  • Оптимизации сборщика мусора.

Именно с Java 8 Oracle отказалась от кодовых имён и ввела семантическую схему версионирования: MAJOR.MINOR.SECURITY. Например, 1.8.0_381 означает:

  • 1.8 — версия Java 8;

  • _381 — уровень security-обновлений;

  • b09 — номер сборки.

Начиная с Java 8u203, Oracle начала применять новую лицензию OTN, которая ограничивает бесплатное использование JDK в продакшене. В ответ на это выросло значение альтернативных поставщиков (Adoptium, Amazon Corretto и др.).


Java 9 (2017)

Java 9 вышла с заметной задержкой, но принесла ключевую архитектурную новацию — Project Jigsaw и модульную систему Java. До этого сборки Java-приложений были монолитны и тяжеловесны. Модули решили эту проблему на уровне платформы.

Изменения были не косметические, а фундаментальные — и многим опытным разработчикам потребовалось время на адаптацию. Модули позволили лучше управлять зависимостями, улучшить безопасность и сократить «раздувание» приложений.

Другие фичи Java 9:

  • JShell — REPL-интерфейс для Java;

  • private-методы в интерфейсах;

  • Улучшения try-with-resources;

  • Новый формат хранения строк (compact strings);

  • Оптимизации GC (G1 по умолчанию);

  • Фабричные методы для коллекций (List.of(...));

  • Обновления API для обработки изображений.


Java 10 (март 2018)

С выходом Java 10 началась новая эра: язык начал развиваться с предсказуемой скоростью, и каждые шесть месяцев стали появляться новые релизы. Это потребовало от разработчиков пересмотра подходов к обновлению и внимательности к тому, какие версии являются LTS (Long-Term Support), а какие — промежуточными, с коротким сроком поддержки.

Java 10 (внутренне обозначалась как Java 18.3) стала первой версией, выпущенной в рамках новой системы нумерации: $YEAR.$MONTH. Это должно было упростить понимание возраста версии, особенно для целей обновлений и безопасности.

Главная новость релиза — это появление var, то есть выведения типа переменной на месте (local-variable type inference).

Кроме var, релиз включал:

  • Оптимизации сборщика мусора;

  • Class-Data Sharing между процессами;

  • Новые возможности работы с памятью (поддержка альтернативных устройств, включая NVRAM);

  • Улучшения в многопоточности.


Java 11 (сентябрь 2018)

Java 11 (или JDK 18.9) — первая LTS-версия в новой модели. Это означало, что она будет поддерживаться Oracle и другими поставщиками на протяжении нескольких лет, а большинство корпоративных пользователей могли делать ставку именно на неё.

Ключевые изменения:

  • Удаление устаревших модулей (CORBA, Java EE);

  • var теперь можно использовать в параметрах лямбда-выражений;

  • Epsilon GC — экспериментальный "нулевой" сборщик мусора, без реальной очистки памяти (использовался для тестов);

  • Новый HTTP Client API — долгожданная замена HttpURLConnection;

  • Deprecation движка Nashorn (JavaScript в Java), который стал слишком сложно поддерживать.

С релизом Java 11 Oracle также начала взимать плату за использование JDK 8 в продакшене, что подтолкнуло рынок к росту популярности альтернативных дистрибутивов Java:

  • AdoptOpenJDK / Eclipse Adoptium — открытые бинарники OpenJDK;

  • Amazon Corretto, Azul Zulu, GraalVM и др.

Код Oracle JDK и OpenJDK практически идентичен, но OpenJDK лицензируется по GPL и может модифицироваться под нужды бизнеса.


Java 12 (март 2019)

Java 12 продолжила шестимесячный ритм. Это не была LTS-версия, но в ней появились важные фичи:

  • Shenandoah GC — новый алгоритм сборки мусора, минимизирующий паузы;

  • Расширенный switch — теперь switch можно использовать как выражение, а не только как оператор:

  • JVM Constants API — для инструментов, работающих с байткодом;

  • Улучшения в G1 GC и Class Data Sharing (CDS);

  • Добавление ~100 микробенчмарков в исходный код JDK.


Java 13 (сентябрь 2019)

Java 13 принес несколько точечных, но важных улучшений:

  • Расширение поддержки Class Data Sharing (CDS) — ускорение запуска приложений за счёт повторного использования данных между процессами;

  • Улучшения ZGC — экспериментального сборщика мусора с очень короткими паузами;

  • Полная реализация нового Legacy Socket API — для более стабильной работы сетевых приложений;

  • Дальнейшее развитие switch-выражений;

  • Первый preview новой фичи — Text Blocks — многострочных строковых литералов, упрощающих работу с форматированием строк (например, HTML или SQL):


Java 14 (март 2020)

Java 14 добавила сразу несколько концептуальных новшеств:

  • Pattern matching для instanceof: теперь можно избежать явного кастинга;

  • Records (предварительный просмотр) — компактная запись классов, хранящих неизменяемые данные;

  • Новый API Foreign Memory Access — безопасная работа с памятью вне кучи Java;

  • Улучшения в NullPointerException — теперь он показывает, какая именно переменная null;

  • Инструмент jpackage для создания нативных установщиков (например, .msi, .dmg, .deb);

  • Расширенная поддержка ZGC, включая macOS;

  • Удаление устаревшего CMS GC.

Многие из этих фич были ещё в preview, и, как и Java 13.


Java 15 (сентябрь 2020)

В Java 15 Oracle окончательно завершила поддержку JavaScript-движка Nashorn, который устарел из-за стремительного развития ECMAScript.

Зато появились новые возможности:

  • Text Blocks официально вышли из preview;

  • Sealed classes (preview) — контроль над наследованием классов через ключевое слово permits;

  • Поддержка криптографии на основе EdDSA;

  • Новые классы DatagramSocket и MulticastSocket;

  • Отключение Biased Locking — это увеличило производительность многопоточных приложений;

  • Records перешли во второй раунд preview.


Java 16 (март 2021)

Java 16, как и предыдущие версии, не получила статус LTS, но в ней были завершены и стабилизированы несколько важных инициатив:

  • Vector API — экспериментальный API для векторных вычислений, с возможностью компиляции в SIMD-инструкции CPU;

  • Полная инкапсуляция внутренних API JDK по умолчанию (начиная с JDK 9 этот процесс шёл поэтапно);

  • Foreign Linker API — безопасный способ вызывать нативные библиотеки из Java-кода без JNI;

  • Elastic Metaspace — динамический возврат неиспользуемой metaspace-памяти операционной системе;

  • Поддержка C++14 в сборке JDK;

  • Records получили финальную, стабильную реализацию.


Java 17 (сентябрь 2021)

Java 17 — один из важнейших релизов последних лет: это LTS-релиз, который будет поддерживаться Oracle минимум до 2029 года. Многие компании именно с этой версии начали новую волну миграций.

Что важного:

  • Доработки Vector API;

  • Финализация sealed classes — контроль над наследованием классов и интерфейсов;

  • Первый preview pattern matching в switch;

  • Улучшения для macOS (адаптация к новой архитектуре и системным ограничениям);

  • Новый API для псевдослучайных чисел (PRNG), включая jumpable и splitable генераторы;

  • Усиление инкапсуляции JDK internals;

  • Объединённый Foreign Function & Memory API — мощная альтернатива JNI для вызова функций Си и работы с "внешней" памятью.


Java 18 (март 2022)

Java 18 — не LTS, но с рядом полезных нововведений:

  • Vector API (третья итерация инкубатора);

  • Simple Web Server — простейший встроенный HTTP-сервер для разработки и тестирования;

  • Новая аннотация @snippet в JavaDoc — для вставки кода напрямую в документацию;

  • Второй preview pattern matching в switch — улучшение читаемости и гибкости;

  • Deprecation механизма finalize(), который будет удалён в будущем — как устаревшего и небезопасного.


Java 19 (сентябрь 2022)

Java 19 — короткоживущий релиз, но с важными фичами в preview:

  • Virtual Threads (предварительный просмотр) — ключевая часть Project Loom: «лёгковесные потоки», реализованные внутри JVM, позволяют обрабатывать тысячи параллельных задач без затрат, характерных для Thread;

  • Record Patterns (1-й preview) — расширение возможностей pattern matching;

  • Pattern Matching для switch (3-й preview);

  • Structured Concurrency (инкубатор) — декларативное управление жизненным циклом задач;

  • Ещё один раунд развития Vector API.


Java 20 (март 2023)

Java 20 продолжила работу над ключевыми направлениями:

  • Virtual Threads (2-й preview);

  • Structured Concurrency (2-й инкубатор);

  • Record Patterns (2-й preview);

  • Scoped Values — новый способ безопасно передавать данные между потоками без ThreadLocal;

  • Обновления Vector API (5-й инкубатор);

  • И очередной preview pattern matching в switch.

Многие из этих фич должны были стать финальными в Java 21.


Java 21 (сентябрь 2023)

Java 21 — LTS-релиз с восьмилетней поддержкой и настоящим букетом зрелых, важных изменений. Именно эта версия стала новой «точкой отсчёта» для многих проектов.

Финализированные фичи:

  • Virtual Threads — теперь стабильно доступны и полностью интегрированы. Потоков стало много и они "дешевле" потоков операционной системы;

  • Pattern Matching для switch — финальная реализация, теперь это не preview;

  • Record Patterns — шаблоны распаковки значений из record-классов;

  • Generational ZGC — новое поколение Z Garbage Collector: теперь он разделяет кучу на молодые и старые объекты для повышения эффективности;

  • Sequenced Collections — новый тип коллекций с гарантированной упорядоченностью (SequencedCollection<E>, SequencedSet<E>).

Preview и инкубаторные фичи:

  • Unnamed Patterns и _-переменные — underscore возвращается (используется как placeholder, как в Go);

  • Unnamed Classes и main-методы — Java, с которой удобно начинать: можно писать main-метод без public class;

  • Structured Concurrency — улучшение читаемости и управляемости параллельных задач.


Java 22 (март 2024)

Java 22 развила инициативы прошлых релизов и представила новые preview-фичи:

  • Unnamed Variables & Patterns — расширение синтаксиса для pattern matching;

  • Statements before super(...) — более гибкое построение тел конструкторов;

  • Class-File API (1-й preview) — новый стандартный API для анализа и трансформации .class-файлов;

  • Запуск многофайловых программ без сборки — удобно для демо и обучения;

  • Stream Gatherers (preview) — расширения Stream API для пользовательских промежуточных операций;

  • Implicitly Declared Classes and Instance Main Methods (2-й preview) — развитие идеи из Java 21: «быстрый старт» без классов и церемоний;

  • Structured Concurrency (2-й preview).


Java 23 (сентябрь 2024)

Java 23 развила начатое ранее и добавила интересные preview-фичи:

  • Primitive Types в instanceof, switch, и patterns — поддержка примитивов в pattern matching;

  • Markdown в Javadoc — наконец-то можно писать документацию в Markdown;

  • Stream Gatherers (2-й preview) — API становится ближе к финализации;

  • Generational ZGC теперь используется по умолчанию;

  • Module Import Declarations — декларативный импорт модулей;

  • Class-File API (2-й preview);

  • Flexible Constructor Bodies (2-й preview);

  • Structured Concurrency (3-й preview);

  • Unnamed Classes and Main Methods (3-й preview).


Java 24 (март 2025)

  • Structured Concurrency (4-й preview) — продолжение шлифовки API для структурированной конкурентности;

  • Поддержка квантово-устойчивых алгоритмов:

    • ML-KEM — капсуляция ключей;

    • ML-DSA — цифровые подписи;

  • Scoped Values (preview) — альтернатива ThreadLocal для безопасного шаринга данных между потоками;

  • Примитивы в pattern matching (2-й preview);

  • Stream Gatherers;

  • Vector API (9-я итерация) — вычисления на уровне SIMD, особенно актуально для AI;

  • Class-File API (финал) — единый стандарт для обработки .class-файлов;

  • Late Barrier Expansion для G1 GC — оптимизация сборщика мусора;

  • Предупреждения о будущем ограничении JNI — подготовка к безопасной замене через FFM API;

  • Ahead-of-time Class Loading & Linking — ускорение старта приложения;

  • Compact Object Headers — уменьшение накладных расходов на уровне JVM;

  • Linking runtime images без JMOD — уменьшение размера JDK в контейнерах.

Итого с 8 по 24 версии платформа Java прошла путь от консервативного языка до современной экосистемы. Предсказуемые релизы, preview-фичи, участие сообщества, масштабные инициативы (Loom, Panama, Valhalla) — всё это превращает Java в платформу, которая умеет меняться, не ломая совместимость.

Как и зачем меняется язык Java

Обсуждая историю Java, важно понимать не только что изменилось, но и почему именно так. За каждой новой возможностью — будь то switch expression или виртуальные потоки — стоят годы инженерной работы, тонкие компромиссы и аккуратный расчёт рисков.

Java не просто «добавляет фичи». Она делает это по определённой шкале затрат и последствий — от изменений в библиотеке до трансформаций JVM. И чем глубже изменение, тем выше цена его реализации.

Вот как можно условно классифицировать изменения по глубине внедрения:

Разумеется, легче всего добавить новый метод в библиотеку. А вот чтобы изменить работу JVM — нужно обновить спецификации, компилятор, формат байткода, виртуальную машину, систему рефлексии, сериализации и десятки инструментов.

Даже "простое" изменение может затронуть множество слоёв:

  • Обновление спецификации языка (JLS);

  • Прототипирование в javac;

  • Поддержка в стандартной библиотеке;

  • Написание тестов, документации;

  • При необходимости — изменения в JVM, classfile, javap, jlink, javadoc и т. д.

Система типов — ещё одна зона сложности: она в Java мощная, но взаимодействие новых конструкций с generics, overloading, аннотациями и лямбдами требует тщательной проработки. Именно поэтому, например, pattern matching реализуется поэтапно, через preview.

Исторически, все изменения в Java проходили через механизм JSR (Java Specification Request) в рамках Java Community Process (JCP). Этот процесс до сих пор используется для ключевых стандартов (например, API в java.*), но он довольно тяжеловесен.

Поэтому в последние годы активно используется JEP (JDK Enhancement Proposal) — более лёгкий процесс, сфокусированный на конкретных фичах. Именно по номерам JEP часто обсуждают нововведения — например, JEP 425 (Virtual Threads) или JEP 440 (Record Patterns). Полный список JEP можно найти здесь: openjdk.org/jeps/0

С выходом Java 12 появилась двухступенчатая модель "тест-драйва" новых возможностей.

Инкубационные фичи - это обычно новые API, поставляемые в виде отдельных модулей, например jdk.incubator.vector. Они изолированы, легко заменимы и могут использоваться даже в проде, если вы готовы следить за изменениями. Например, HTTP/2 API, сначала был jdk.incubator.http, позже стал java.net.http.

Preview-фичи - это языковые конструкции или изменения JVM, встроенные в платформу, но требующие включения через флаги:

javac --enable-preview --release 21 Hello.java 
java --enable-preview Hello

Они не предназначены для продакшена, так как:

  • Не гарантируют обратную совместимость;

  • Могут быть изменены или удалены;

  • Часто используют нестабильные форматы .class-файлов.

Preview — это инструмент для экспериментов и обратной связи, чтобы нововведения успели пройти "обкатку" до финализации.

Зачем вся эта сложность? Потому что Java — это не просто язык. Это огромная экосистема с миллионами строк продакшн-кода. И каждое изменение должно быть не только полезным, но и безопасным, предсказуемым, обратимо внедряемым. Именно поэтому Java и остаётся одной из самых надёжных и стабильных платформ.

История оптимизаций JVM

Что же, история версий показывает, как Java шаг за шагом становилась современным, выразительным и мощным языком. Но за эволюцией языка стоит не менее захватывающая история развития самой виртуальной машины Java (JVM) — той самой «невидимой инфраструктуры», которая превращает байткод в работающее приложение.

Чтобы понять, почему Java остаётся конкурентоспособной в эпоху нативных AOT-компиляторов и hyperscaler-платформ, стоит заглянуть под капот JVM. Это поможет оценить, как именно достигается производительность, какие компромиссы делает платформа и куда она движется дальше — например, в рамках проектов вроде Leyden, нацеленного на быстрый старт и малый memory footprint.

Разберёмся, как исполняется код на JVM, как работает HotSpot и почему JIT-компиляция — это не магия, а инженерное искусство.

На сегодняшний день Java — одна из крупнейших технологических платформ в мире. По разным оценкам, с ней работают более 10 миллионов разработчиков. Но далеко не все из них знакомы с тем, как именно исполняется их код. И это неудивительно: архитектура JVM изначально была спроектирована так, чтобы скрывать сложность от разработчика — управление памятью, оптимизации исполнения и другие "низкоуровневые" заботы берет на себя сама виртуальная машина.

Тем не менее, если вы пишете производительный код, дебажите latency на проде или просто хотите лучше понимать, почему Java ведет себя именно так, — стоит разобраться в базовых принципах работы JVM.

Виртуальная машина, байткод и classloaders

JVM — это стековая интерпретируемая машина. Вместо регистров, как у "железа", она использует стек значений: инструкции работают с вершиной этого стека. Ментально вы можете представить её как бесконечный switch-case внутри while, где каждая команда (opcode) обрабатывается по одной.

Когда вы запускаете java HelloWorld, происходит следующее:

  1. ОС запускает бинарник java;

  2. JVM инициализирует себя и загрузчики классов;

  3. Ищется метод main() — именно с него начинается выполнение;

  4. Класс HelloWorld должен быть загружен в память — этим занимается цепочка загрузчиков классов:

    • Bootstrap class loader (он же primordial) — загружает java.base и базовые модули (java.lang.Object, ClassLoader, и т.д.);

    • Platform class loader — отвечает за "основу" JDK, заменяя старый ExtClassLoader;

    • Application class loader — загружает ваш код из classpath.

С Java 9 JVM стала полностью модульной. Даже если вы не используете модули явно, JVM всё равно собирает модульный граф зависимостей (DAG) при запуске.

Как JVM исполняет байткод

JVM не работает с исходным Java-кодом напрямую. Он сначала компилируется javac в .class-файлы — промежуточный байткод, который:

  • Не привязан к конкретной архитектуре (в отличие от нативного кода);

  • Может быть выполнен на любой платформе, где есть JVM;

  • Является промежуточным слоем между языком и машиной.

Каждый .class-файл начинается с магического числа 0xCAFEBABE и содержит:

  • Версию формата;

  • Constant pool;

  • Метаданные: флаги доступа, суперкласс, интерфейсы;

  • Методы, поля, атрибуты (включая байткод в атрибуте Code).

JVM валидирует байткод перед выполнением, проверяя корректность структуры, доступа и ссылок.

HotSpot и JIT: магия производительности

С апреля 1999 года основной реализацией JVM стал HotSpot — виртуальная машина, умеющая динамически компилировать байткод в нативный код во время исполнения. Этот процесс называется Just-in-Time (JIT) compilation.

Как это работает:

  • Сначала байткод интерпретируется;

  • HotSpot профилирует выполнение и отслеживает "горячие участки" (чаще всего вызываемые методы и циклы);

  • Когда некий метод исполняется достаточно часто — он компилируется в нативный код;

  • Компилятор JIT может повторно перекомпилировать код при изменении условий исполнения, чтобы применить более выгодные оптимизации.

HotSpot умеет:

  • Инлайнить методы;

  • Удалять ненужные виртуальные вызовы;

  • Специализировать код под конкретный CPU, на котором он запущен (так называемые intrinsics).

И всё это — прозрачно для разработчика: вам не нужно писать вручную оптимизации, просто пишите "обычный Java-код".

HotSpot — это компромисс между "удобством разработки" и "низкоуровневой оптимальностью":

  • Противоположный подход — как в C++: всё под контролем, но вся сложность на совести разработчика;

  • В Java вы получаете хорошую производительность и при этом фокусируетесь на бизнес-логике, а не на кешах и регистрах.

Однако это значит, что поведение Java-приложения в проде может отличаться от того, что вы видите в коде. Поэтому любые разговоры о производительности требуют понимания JVM.

Эволюция оптимизаций производительности

С ростом требований к быстродействию, особенно в облачных или serverless-сценариях, внимание к времени запуска и времени достижения пиковой производительности (time to steady state) стало критически важным. Далее мы рассмотрим, как JVM эволюционировала, чтобы минимизировать эти метрики.

Типичное приложение на Java проходит несколько фаз:

  1. Запуск – инициализация JVM, загрузка и инициализация классов, запуск main().

  2. Разогрев (Ramp-up) – приложение начинает выполнять полезную нагрузку, а JIT-компилятор постепенно оптимизирует часто используемые методы.

  3. Стабильное состояние – производительность стабилизируется, код работает в оптимизированной форме.

  4. Завершение – освобождение ресурсов и остановка JVM.

Эти фазы важны, так как в краткоживущих или облачных приложениях чем быстрее приложение выходит на пиковую производительность, тем лучше. Здесь особенно важна грамотная работа с "состоянием" приложения – его переменными, потоками, объектами в куче и используемыми ресурсами.

Class Data Sharing (CDS)

CDS — это одна из старейших и самых недооценённых технологий оптимизации JVM. Суть в следующем:

  • При первом запуске классы приложения могут быть сериализованы во внутренний формат JVM в специальный архив.

  • Этот архив затем используется при последующих запусках, позволяя JVM сразу работать с готовыми структурами, минуя фазу анализа и валидации классов.

  • Поддерживается шаринг между несколькими JVM-инстансами на одной машине, что снижает потребление памяти и ускоряет запуск.

Особенно полезно для многократных запусков функций в serverless-среде или контейнерах.

Ahead-of-Time Compilation (AOT)

Введённая в экспериментальном виде в JDK 9, AOT-компиляция позволяет преобразовывать байткод Java в нативный машинный код до запуска JVM. Это особенно актуально для краткоживущих приложений, где JIT не успевает "разогнаться".

Но у AOT в HotSpot были ограничения:

  • Необходимость компиляции в PIC (position-independent code).

  • Сложности интеграции с Graal-компилятором.

  • Ограничения на оптимизацию по сравнению с JIT.

Project Leyden

В ответ на сложности с AOT был запущен Project Leyden (начиная с JDK 17). Его цель — оптимизация полного жизненного цикла Java-приложений, включая:

  • Предварительную компиляцию (precompilation) с использованием так называемого "тренировочного запуска".

  • Создание преднастроенного образа выполнения, содержащего классы, инициализированные данные и JIT-оптимизированный код.

  • Введение "конденсаторов" — механизмов, которые позволяют сдвигать выполнение логики из времени исполнения в фазу подготовки.

Это похоже на то, как Java-код может "переехать" в JIT-оптимизированный и запакованный образ, подгружаемый за миллисекунды.

GraalVM и нативные образы

GraalVM представляет собой другой подход к решению той же задачи:

  • Поддержка AOT-компиляции с генерацией нативных исполняемых файлов (native-image).

  • Исключение JVM-интерпретатора и JIT из пути запуска.

  • Возможность мгновенного старта и быстрого выхода на пиковую производительность.

Под капотом — Graal-компилятор, способный применять спекулятивные оптимизации и даже выполнять "самооптимизацию" в рантайме.

CRIU и Project CRaC

Следующий шаг — сохранение состояния JVM-приложения на диск и его последующее восстановление:

  • CRIU (Checkpoint/Restore in Userspace) позволяет "заморозить" процесс Java, сохранить его в файл и позже "оживить".

  • Project CRaC (Coordinated Restore at Checkpoint) расширяет эту возможность, интегрируя сохранение/восстановление состояния прямо в JVM.

В перспективе это может обеспечить почти мгновенный старт сложных приложений, загруженных с готовыми состояниями объектов, потоков и классов.

В итоге сегодня производительность Java — это не просто вопрос JIT-компиляции. Это развитие таких оптимизаций как CDS, AOT, CRaC, GraalVM и Project Leyden, каждая из которых дополняет друг друга.

Java больше не просто "второй после C++". В облачной эпохе она становится платформой первого класса, способной к мгновенному запуску и высокой производительности.

Заключение

История Java — это история постоянной эволюции. От первых версий JDK, где всё только начиналось, до современных технологий, вроде Project Leyden, GraalVM и CRaC, платформа Java прошла путь от интерпретатора до высокоэффективной, гибкой и масштабируемой среды исполнения.

Мы увидели, как язык развивался синтаксически и архитектурно, как менялись его версии, как JVM превращалась из простой виртуальной машины в сложный механизм, способный динамически адаптироваться к рабочей нагрузке и окружению. Вместе с этим росло и сообщество, вносящее свой вклад через JEP и JSR, поддерживающее OpenJDK, развивающее новые возможности и стандарты.

Но что важнее — Java не стоит на месте. Вместо того чтобы почивать на лаврах, она продолжает двигаться вперёд, сохраняя обратную совместимость и при этом внедряя передовые идеи, вдохновлённые другими языками и инженерными практиками.

Поэтому, изучая Java, вы изучаете не просто язык, а живую технологию, отражающую дух времени и стремление сделать разработку одновременно удобной и мощной. Это не просто инструмент программиста — это один из краеугольных камней современной инженерии.


Чтобы еще больше проникнуться вайбом истории Java можно посмотреть мой доклад "Краткая история Java", с которым я выступал на HackConf:
Смотреть на YouTube

Комментарии (11)


  1. erydit
    23.05.2025 08:32

    И, как и 30 лет назад, на Java работают 3 миллиарда устройств. Стабильность или стагнация?)


    1. RustamKuramshin Автор
      23.05.2025 08:32

      Главное, что так было всегда )


  1. Lewigh
    23.05.2025 08:32

    Но что важнее — Java не стоит на месте. Вместо того чтобы почивать на лаврах, она продолжает двигаться вперёд, сохраняя обратную совместимость и при этом внедряя передовые идеи, вдохновлённые другими языками и инженерными практиками.

    Ох лучше бы стояла на месте. Шучу. Но на самом деле многое из того что пошло после 11 релиза вызывает недоумение. Виртуальные потоки это однозначный плюс, если не учитывать что больше чем 10 лет уже как в другие языках все это давно есть. Но вот чем чем была хорошая Java это компромиссом между сложностью, производительностью, удобством и скоростью обучения. Многие десятилетия они бережно выверяли каждую фичу и лозунг был что-то в духе - лучше не добавлять ничего чем добавить лишнее. И вот с появлением коротких релизных циклов язык начинают ломать об колено: давайте сделаем два разных синтаксиса для switch, давайте сделаем альтернативный синтаксис для классов и назовем record как в C#, давайте за пару релизов добавим кучу ключевых слов. Мне кажется сила Java было в минималистичности и простоте, сейчас пошел тренд на завоз сахара вагонами чтобы догонять модные языки все это в ущерб простоте. Стратегия не совсем понятная как как уже есть Kotlin и мне кажется не совсем правильная.


    1. RustamKuramshin Автор
      23.05.2025 08:32

      Ну, Java никогда не была ортогональным языком как Go, например. Всегда можно было делать что-то несколькими способами. По мне это не хорошо и неплохо, просто по такому пути развитие пошло. Это открывает возможности, но и может запутать.


    1. snuk182
      23.05.2025 08:32

      Ничто не мешает этим всем не пользоваться, старый синтаксис работает, даже бестипные коллекции на месте, хоть и ругаются при сборке.


      1. RustamKuramshin Автор
        23.05.2025 08:32

        Коллекции без дженериков - это уже перебор


        1. snuk182
          23.05.2025 08:32

          Тем не менее, работают. Более того, в случае особо лютого преобразования типов к ним приходится кастить, самостоятельно компилятор падает в обморок.


        1. misha_ruchk0
          23.05.2025 08:32

          на 2008 год, когда я последний раз заглядывал под капот javac, по сути дженерики существовали до байткода.


      1. JediPhilosopher
        23.05.2025 08:32

        Этот аргумент всегда высказывают в обсуждениях вида "язык ХХХ - переусложненное непонятное Г". И всегда на него отвечают, что так оно не работает. Если вы пишете маленький пет-проект то вы еще можете избежать всей сложности. Но как только вы приходите в проект большой, разрабатываемый разными командами, и еще и использующий миллион зависимостей - вам придется изучить все самые извращенные практики, неочевидные конструкции языка и сложные навороты. Потому что не вы так ваш коллега, а не коллега - так поставщик зависимости, будет их использовать в своем коде, и вам все равно придется в нем разбираться.


    1. Shatun
      23.05.2025 08:32

      Виртуальные потоки это однозначный плюс, если не учитывать что больше чем 10 лет уже как в другие языках все это давно есть

      И много языков где есть в таком же виде? Я сходу назову только го. Остальные языки в основном реализовали async/await как аналог

      давайте сделаем два разных синтаксиса для switch

      Которые прямо сильно упрощает код, для меня фича приятнее чем вирутальные потоки

      альтернативный синтаксис для классов и назовем record как в C#

      тут скорее в обратную сторону, рекорды, по крайней мере публично в джаве раньше появилсись. Правда из-за превью фич и лтс до прода по итогу в шарпах раньше пошли.

      сейчас пошел тренд на завоз сахара

      По мне как раз сахара и не завозят. пятилетку пилили виртруальные потоки, вальхалла пилится уже десятилетие, патерн матчинг, 2 новых GC и т.д - куча больших серъезных фич. При этом стринг темплейты выкинули, стандартная библиотека для джсона уже лет 10 висит в разработке, теперь вроде бы ждет вальхаллу, with для рекордов хотят когда-нибудь запилить, а без него они их полезность сильно падает, именованые параметры говорят наверное когда-нибудь, optional chaining вроде никто не против, но делать не собираются и т.д.


  1. lgorSL
    23.05.2025 08:32

    На мой взгляд очень давно не хватает разделения на nullable / non-nullable типы на уровне байткода jvm (и его верификации), кажется это позволит JIT работать эффективнее и код будет более строгий.
    Сейчас пытаются сделать value типы и видно, что возможность вернуть null там сильно мешает, всё равно придётся переделать.