Если вы в последнее время следили за новостями в мире Java, вы, вероятно, слышали, что последняя сборка Java 8, выпущенная Oracle, Java 8u11 (и Java 7u65), привела к ошибкам и сломала некоторые популярные сторонние инструменты, такие как JRebel, Javassist, Google Guice от ZeroTurnaround и даже Groovy.
Ошибки, выдаваемые JVM, длинные и подробные, но по сути они выглядят примерно так:
1
2
3
4
5
|
Exception in thread "main" java.lang.VerifyError: Bad method call from inside of a branch Exception Details: Location: com/takipi/tests/dc/DepthCounter.()V @10 : invokespecial … |
Причина, по которой эти ошибки неожиданно стали появляться, связана с тем, что верификатор байт-кода в последних обновлениях немного более строг, чем в предыдущих версиях. В отличие от предыдущих версий, он не позволяет вызывать супер-конструкторы из разветвленного кода.
Давайте разберемся с этим.
Java-байт-код и верификатор байт-кода
Байт-код является промежуточным языком, который фактически выполняет JVM и на котором записаны скомпилированные файлы .class . Машинный код JVM, если хотите.
Все языки на основе JVM компилируются в байт-код, от Java, до Scala, Groovy, Clojure и так далее. JVM не знает и не заботится о том, каким был исходный язык — он знает только байт-код.
Я не буду вдаваться в подробности того , как работает байт-код , поскольку это тема, достойная отдельной публикации (или нескольких сообщений), а просто для того, чтобы понять, как выглядит байт-код — возьмем, к примеру, этот простой Java-метод:
1
2
3
4
|
int add( int x, int y) { int z = x + y; return z; } |
При компиляции его байт-код выглядит так:
1
2
3
4
5
6
|
ILOAD x ILOAD y IADD ISTORE z ILOAD z IRETURN |
Когда JVM загружает файл класса из пути к классам в память, он должен сначала убедиться, что байт-код действителен и что код структурирован правильно. Он в основном проверяет, действительно ли загружаемый код может быть выполнен. Если байт-код исправен, класс успешно загружен в память; в противном случае выдается ошибка VerifyError , как и в начале сообщения.
Этот процесс называется проверкой байт-кода, и часть JVM, отвечающая за него, является верификатором байт-кода.
Почему это сломалось?
Чтобы байт-код прошел проверку, он должен соответствовать набору правил, определенных в спецификации формата файла класса. Поскольку JVM изначально была разработана с учетом языка программирования Java, многие из этих правил напрямую вытекают из правил и ограничений Java.
Одним из таких хорошо известных ограничений в языке Java является то, что самое первое, что вы должны сделать в конструкторе, прежде чем делать что-либо еще, — это вызвать super (…) или this (…) . Любой кусок кода до этого — и ваш код не скомпилируется. Даже если вы явно не пишете super () , компилятор неявно вставляет его для вас в самом начале конструктора.
Такое же ограничение существует, по крайней мере, на бумаге, в правилах проверки байт-кода . Однако оказывается, что до этих последних обновлений JDK это ограничение не было полностью применено. Это означает, что хотя ни один компилятор Java никогда не позволил бы вам скомпилировать этот код:
1
2
3
4
5
6
7
8
9
|
public static class ClassyClass { public ClassyClass() { if (checkSomething()) { super (); } else { super (getSomething()); } } } |
… Эквивалентный байт-код прошел бы проверку!
1
2
3
4
5
6
7
8
|
ALOAD this INVOKESTATIC checkSomething() : boolean IFEQ L2 INVOKESPECIAL super () : void GOTO L2 L1: INVOKESTATIC getSomething() : int INVOKESPECIAL super ( int ) : void L2: RETURN |
Вы можете видеть в упрощенном байт-коде выше, что есть и вызов ( INVOKESTATIC ), и даже ветвь ( IFEQ — «если равно»), выполняемая перед первым вызовом супер-конструктора ( INVOKESPECIAL ).
Имейте в виду, что, хотя приведенный выше код не является допустимой Java, и, следовательно, ни один компилятор Java никогда не будет генерировать эквивалентный байт-код — существует множество других потенциально возможных инструментов, таких как компиляторы других языков JVM, которые не следуют ограничениям Java, и многие другие инструменты, такие как библиотеки инструментария байт-кода. Возможность выполнения кода перед вызовом super может быть довольно полезной!
Однако в обновлении 11 для Java 8 появился более строгий верификатор байт-кода, который отвергает классы, использующие такие конструкции в своем байт-коде, и приводит к возникновению ошибок проверки и сбоям JVM.
С одной стороны, новый верификатор лоялен к спецификации, гарантируя, что наши JVM защищены от плохого кода. С другой стороны, многие инструменты, которые используют инструментарий байт-кода, такие как отладчики и ткачи аспекта (AOP), часто используют конструкции, подобные приведенным выше.
Как это решить?
Исправление для верификатора байт-кода уже зафиксировано , но еще не выпущено. Однако многие из затронутых инструментов и проектов уже выпускают исправленные версии и обходные пути.
Тем временем, если вы столкнулись с одной из этих ошибок, вы можете попробовать запустить JVM с аргументом командной строки -noverify . Эта опция указывает JVM пропускать проверку байт-кода при загрузке классов.
Ссылка: | Последнее обновление Oracle 8 для Java сломало ваши инструменты — как это случилось? от нашего партнера JCG Нива Штейнгартена в блоге Takipi . |