← На главную

JVM вводит strictly-initialized поля — явная инициализация в байткоде

02.07.2026 18:56 · hackernews

JVM вводит строго инициализируемые поля — strictly-initialized. Они не получают значений по умолчанию (0, false, null). Поле должно быть явно установлено в байткоде до первого чтения. Если поле ещё и final, каждое чтение будет возвращать одно и то же значение. Это preview-фича, доступная компиляторам, которые генерируют class-файлы.

Проблема со значениями по умолчанию знакома каждому Java-разработчику. Код может прочитать null из поля и передать его дальше, а NullPointerException вылетит совсем в другом месте. Ещё хуже с final-полями во время инициализации класса: пока класс не завершил <clinit>, поле может быть прочитано с дефолтным значением. В статье разобран классический пример с App.appID и Log.prefix — из-за круговой зависимости appID временно равен 0, и этот 0 попадает в строку префикса.

Новая модель решает обе проблемы. Поле помечается флагом ACC_STRICT_INIT (0x0800) в class-файле. Для статических полей JVM отслеживает состояние инициализации класса (larval). Если getstatic пытается прочитать строгое поле, которое ещё не установлено — бросается исключение. putstatic на final-поле после того, как его уже прочитали — тоже исключение. При завершении <clinit> все строгие поля должны быть установлены, иначе класс не перейдёт в initialized.

Для полей экземпляра работает байткод-верификатор. Он использует новый тип стек-фрейма early_larval_frame, который хранит список ещё не установленных строгих полей. putfield разрешён только в ранне-личиночном состоянии (early-larval). getfield в этом состоянии запрещён — так что прочитать незаданное поле нельзя. putfield на строгом final-поле — только в early-larval, после вызова super() и перехода в late-larval менять его уже нельзя.

На эти поля опираются будущие языковые фичи: value-классы (экземпляры без идентичности) и null-restricted поля (никогда не содержат null). Для их корректной работы строгая инициализация обязательна.

Deep reflection (setAccessible + set) на строгих final-полях теперь всегда бросает IllegalAccessException — даже с включённым --enable-final-field-mutation. Десериализация через ObjectInputStream тоже невозможна: если класс объявляет строгие поля и не является record, методы writeObject/readObject кидают InvalidClassException. Нужно писать writeReplace и readResolve, чтобы подставлять объект-заменитель.

Инструментарий обновлён: javap показывает флаг и early_larval_frame, java.lang.classfile API поддерживает новый тип записи, AsmTools тоже. Для включения фичи нужен ключ --enable-preview при запуске. Риски стандартны для preview — возможные ошибки установки флага, но конфликт с устаревшим strictfp маловероятен из-за разных версий class-файлов.

Читать оригинал →