Существуют ли в настоящее время (Java 6) вещи, которые вы можете сделать в байт-коде Java, которые вы не можете сделать из языка Java?
Я знаю, что оба Тьюринга завершены, поэтому читать «можно сделать», как «можно сделать значительно быстрее / лучше, или просто по-другому».
Я думаю о дополнительных байтовых кодах invokedynamic
, которые не могут быть сгенерированы с помощью Java, за исключением того, что конкретный предназначен для будущей версии.
rol
в ассемблере, который вы не можете написать на C ++.(x<<n)|(x>>(32-n))
сrol
инструкцией.Ответы:
Насколько я знаю, в байт-кодах, поддерживаемых Java 6, отсутствуют основные функции, которые также недоступны из исходного кода Java. Основная причина этого заключается в том, что байт-код Java был разработан с учетом языка Java.
Однако есть некоторые функции, которые не создаются современными компиляторами Java:
ACC_SUPER
Флаг :Это флаг, который может быть установлен для класса и определяет, как
invokespecial
обрабатывается конкретный угловой случай байт-кода для этого класса. Он устанавливается всеми современными компиляторами Java (где «современный» -> = Java 1.1, если я правильно помню), и только древние компиляторы Java создавали файлы классов, где это не было установлено. Этот флаг существует только по причинам обратной совместимости. Обратите внимание, что начиная с Java 7u51, ACC_SUPER полностью игнорируется по соображениям безопасности.В
jsr
/ret
байткодах.Эти байт-коды использовались для реализации подпрограмм (в основном для реализации
finally
блоков). Они больше не производятся начиная с Java 6 . Причина их устаревания заключается в том, что они значительно усложняют статическую проверку без большой выгоды (т. Е. Используемый код почти всегда может быть повторно реализован с обычными переходами с очень небольшими накладными расходами).Наличие двух методов в классе, которые отличаются только типом возвращаемого значения.
Спецификация языка Java не допускает двух методов в одном классе, если они отличаются только по типу возвращаемого значения (т. Е. Одно и то же имя, один и тот же список аргументов, ...). Спецификация JVM, однако, не имеет такого ограничения, поэтому файл класса может содержать два таких метода, просто нет способа создать такой файл класса, используя обычный компилятор Java. В этом ответе есть хороший пример / объяснение .
источник
a
а другой -A
в него. Мне потребовалось около получаса, чтобы разархивировать на компьютере с Windows, прежде чем я понял, где пропали классы. :)'.'
,';'
,'['
или'/'
. Имена методов одинаковы, но они также не могут содержать'<'
или'>'
. (С заметными исключениями<init>
и,<clinit>
например, статических конструкторов.) Я должен отметить, что если вы строго следуете спецификации, имена классов на самом деле гораздо более ограничены, но ограничения не применяются."throws ex1, ex2, ..., exn"
сигнатуры методов; Вы не можете добавлять предложения исключения в переопределенные методы. НО, JVM не волнует. Таким образом,final
JVM действительно гарантирует, что только методы свободны от исключений - кромеRuntimeException
s иError
s, конечно. Вот вам и проверенная обработка исключений: DПосле долгой работы с байт-кодом Java и некоторых дополнительных исследований по этому вопросу, вот краткое изложение моих выводов:
Выполнить код в конструкторе перед вызовом супер-конструктора или вспомогательного конструктора
В языке программирования Java (JPL) первым оператором конструктора должен быть вызов суперконструктора или другого конструктора того же класса. Это не относится к байт-коду Java (JBC). В байт-коде абсолютно законно выполнять любой код перед конструктором, если:
Установите поля экземпляра перед вызовом супер-конструктора или вспомогательного конструктора
Как упоминалось ранее, совершенно правильно установить значение поля экземпляра перед вызовом другого конструктора. Существует даже устаревший хак, который позволяет использовать эту «функцию» в версиях Java до 6:
Таким образом, поле может быть установлено до вызова супер-конструктора, что, однако, более невозможно. В JBC такое поведение все еще можно реализовать.
Разветвите вызов супер-конструктора
В Java невозможно определить вызов конструктора как
До Java 7u23, однако, верификатор HotSpot VM пропустил эту проверку, поэтому это было возможно. Это использовалось несколькими инструментами генерации кода как своего рода хак, но реализация такого класса уже недопустима.Последний был просто ошибкой в этой версии компилятора. В новых версиях компилятора это снова возможно.
Определить класс без конструктора
Компилятор Java всегда реализует по крайней мере один конструктор для любого класса. В байт-коде Java это не требуется. Это позволяет создавать классы, которые не могут быть построены даже при использовании отражения. Тем не менее, использование по-
sun.misc.Unsafe
прежнему позволяет создавать такие экземпляры.Определите методы с одинаковой подписью, но с другим типом возврата
В JPL метод идентифицируется как уникальный по имени и типам необработанных параметров. В JBC необработанный тип возврата дополнительно рассматривается.
Определите поля, которые не отличаются по имени, но только по типу
Файл класса может содержать несколько полей с одинаковыми именами, если они объявляют другой тип поля. JVM всегда ссылается на поле как кортеж имени и типа.
Бросьте необъявленные проверенные исключения, не ловя их
Среда выполнения Java и байт-код Java не знают о концепции проверяемых исключений. Только компилятор Java проверяет, что проверенные исключения всегда либо перехватываются, либо объявляются, если они выброшены.
Использовать динамический вызов метода вне лямбда-выражений
Так называемый динамический вызов метода может использоваться для чего угодно, не только для лямбда-выражений Java. Использование этой функции позволяет, например, отключить логику выполнения во время выполнения. Многие динамические языки программирования, которые сводятся к JBC, улучшили свою производительность с помощью этой инструкции. В байт-коде Java вы также можете эмулировать лямбда-выражения в Java 7, где компилятор еще не допускал никакого использования динамического вызова метода, в то время как JVM уже поняла инструкцию.
Используйте идентификаторы, которые обычно не считаются законными
Вы когда-нибудь мечтали использовать пробелы и разрыв строки в имени вашего метода? Создайте свой собственный JBC и удачи в проверке кода. Единственный недопустимые символы для идентификаторов
.
,;
,[
и/
. Кроме того, методы, которые не названы<init>
или<clinit>
не могут содержать<
и>
.Переназначить
final
параметры илиthis
ссылкуfinal
параметры не существуют в JBC и, следовательно, могут быть переназначены. Любой параметр, включаяthis
ссылку, хранится только в простом массиве в JVM, что позволяет переназначитьthis
ссылку на индекс в0
пределах одного фрейма метода.Переназначить
final
поляПока конечное поле назначается в конструкторе, допустимо переназначить это значение или даже вообще не присваивать значение. Следовательно, следующие два конструктора являются допустимыми:
Для
static final
полей даже разрешено переназначать поля вне инициализатора класса.Обрабатывайте конструкторы и инициализатор класса так, как если бы они были методами
Это скорее концептуальная особенность, но конструкторы в JBC не трактуются иначе, чем обычные методы. Только верификатор JVM гарантирует, что конструкторы вызывают другой допустимый конструктор. Кроме этого, это просто соглашение об именах Java, что конструкторы должны вызываться и вызываться
<init>
инициализатор класса<clinit>
. Помимо этой разницы, представление методов и конструкторов идентично. Как отметил Хольгер в комментарии, вы можете даже определить конструкторы с типами возвращаемых данных, отличными отvoid
инициализатора класса с аргументами, даже если невозможно вызвать эти методы.Создать асимметричные записи * .
При создании записи
javac сгенерирует файл класса с одним именем
bar
, методом доступаbar()
и одним конструкторомObject
. Кроме того, добавлен атрибут записи дляbar
. Создавая запись вручную, можно создать конструктор другой формы, пропустить поле и реализовать средство доступа по-разному. В то же время, все еще возможно заставить API отражения полагать, что класс представляет фактическую запись.Вызовите любой супер метод (до Java 1.1)
Однако это возможно только для версий Java 1 и 1.1. В JBC методы всегда отправляются с явным целевым типом. Это означает, что для
можно было реализовать
Qux#baz
для вызоваFoo#baz
при перепрыгиванииBar#baz
. Хотя все еще возможно определить явный вызов для вызова другой реализации супер-метода, чем у прямого суперкласса, это больше не имеет никакого эффекта в версиях Java после 1.1. В Java 1.1 это поведение контролировалось путем установкиACC_SUPER
флага, который включал бы то же поведение, которое только вызывает реализацию прямого суперкласса.Определить не виртуальный вызов метода, который объявлен в том же классе
В Java невозможно определить класс
Приведенный выше код всегда приводит к тому, что
RuntimeException
когдаfoo
вызывается экземплярBar
. Невозможно определитьFoo::foo
метод для вызова его собственногоbar
метода, который определен вFoo
. Посколькуbar
это не частный метод экземпляра, вызов всегда виртуальный. С байт - код, можно , однако определить вызов использоватьINVOKESPECIAL
опкод , который непосредственно связываетbar
вызов метода вFoo::foo
кFoo
версии «S. Этот код операции обычно используется для реализации вызовов супер-методов, но вы можете повторно использовать код операции для реализации описанного поведения.Мелкозернистые аннотации
В Java аннотации применяются в соответствии с тем,
@Target
что аннотации объявляют. Используя манипулирование байтовым кодом, можно определять аннотации независимо от этого элемента управления. Кроме того, например, можно аннотировать тип параметра без аннотирования параметра, даже если@Target
аннотация применяется к обоим элементам.Определите любой атрибут для типа или его членов
В языке Java можно определять аннотации только для полей, методов или классов. В JBC вы можете встраивать любую информацию в классы Java. Однако, чтобы использовать эту информацию, вы больше не можете полагаться на механизм загрузки классов Java, но вам нужно самостоятельно извлечь метаинформацию.
Перепускные и неявно Присвоить
byte
,short
,char
иboolean
значенияПоследние типы примитивов обычно не известны в JBC, но определяются только для типов массивов или для дескрипторов полей и методов. В инструкциях байт-кода все именованные типы занимают 32-битное пространство, что позволяет представлять их как
int
. Официально, толькоint
,float
,long
иdouble
типы существуют в байт - код , который всем необходимо явное преобразование по правилу испытателя в JVM в.Не выпускать монитор
synchronized
Блок на самом деле состоит из двух утверждений, одно приобретение и один , чтобы выпустить монитор. В JBC вы можете приобрести его, не выпуская его.Примечание . В недавних реализациях HotSpot это приводит к
IllegalMonitorStateException
завершению метода или к неявному освобождению, если метод завершается самим исключением.Добавить более одного
return
оператора в инициализатор типаВ Java даже тривиальный инициализатор типа, такой как
незаконно В байтовом коде инициализатор типа обрабатывается так же, как и любой другой метод, т. Е. Операторы return могут быть определены где угодно.
Создать неприводимые циклы
Компилятор Java преобразует циклы в операторы goto в байт-коде Java. Такие операторы могут использоваться для создания неприводимых циклов, чего никогда не делает компилятор Java.
Определить рекурсивный блок catch
В байт-коде Java вы можете определить блок:
Подобное утверждение создается неявно при использовании
synchronized
блока в Java, где любое исключение при освобождении монитора возвращает к инструкции по освобождению этого монитора. Как правило, в такой инструкции не должно возникать никаких исключений, но если она будет (например, устарелаThreadDeath
), монитор все равно будет освобожден.Вызовите любой метод по умолчанию
Компилятор Java требует выполнения нескольких условий, чтобы разрешить вызов метода по умолчанию:
B
расширяет интерфейс,A
но не переопределяет методA
, метод все равно может быть вызван.Для байт-кода Java учитывается только второе условие. Первый, однако, не имеет значения.
Вызовите метод super для экземпляра, который не
this
Компилятор Java позволяет вызывать метод super (или интерфейс по умолчанию) только в случаях
this
. В байтовом коде также возможно вызвать метод super для экземпляра того же типа, подобного следующему:Доступ к синтетическим членам
В байт-коде Java можно получить прямой доступ к синтетическим членам. Например, рассмотрим, как в следующем примере
Bar
осуществляется доступ к внешнему экземпляру другого экземпляра:Как правило, это верно для любой синтетической области, класса или метода.
Определить несинхронизированную информацию общего типа
Хотя среда выполнения Java не обрабатывает универсальные типы (после того, как компилятор Java применяет стирание типов), эта информация все еще привязывается к скомпилированному классу как метаинформация и становится доступной через API отражения.
Верификатор не проверяет согласованность этих
String
значений, закодированных в метаданных. Следовательно, можно определить информацию об универсальных типах, которая не соответствует стиранию. Как следствие, следующие утверждения могут быть верными:Кроме того, подпись может быть определена как недопустимая, так что возникает исключение времени выполнения. Это исключение выдается, когда к информации обращаются впервые, поскольку она оценивается лениво. (Аналогично значениям аннотации с ошибкой.)
Добавлять метаинформацию только для определенных методов
Компилятор Java позволяет встраивать имя параметра и информацию модификатора при компиляции класса с
parameter
включенным флагом. В формате файла класса Java эта информация сохраняется для каждого метода, что позволяет встраивать такую информацию о методе только для определенных методов.Запутать вещи и крушение вашей JVM
Например, в байт-коде Java вы можете определить, что вызывать любой метод любого типа. Обычно верификатор будет жаловаться, если тип не знает такого метода. Однако, если вы вызываете неизвестный метод в массиве, я обнаружил ошибку в какой-то версии JVM, когда верификатор пропустит это, и ваша JVM завершит работу после вызова инструкции. Хотя это вряд ли особенность, но технически это то, что невозможно с помощью Java скомпилированного javac . У Java есть своего рода двойная проверка. Первая проверка применяется компилятором Java, вторая - JVM при загрузке класса. Пропустив компилятор, вы можете найти слабое место в проверке верификатора. Хотя это скорее общее утверждение, чем особенность.
Аннотируйте тип получателя конструктора, когда нет внешнего класса
Начиная с Java 8, нестатические методы и конструкторы внутренних классов могут объявлять тип получателя и аннотировать эти типы. Конструкторы классов верхнего уровня не могут аннотировать свой тип получателя, так как большинство из них не объявляют его.
Однако, поскольку
Foo.class.getDeclaredConstructor().getAnnotatedReceiverType()
он возвращаетAnnotatedType
представлениеFoo
, можно включить аннотации типов дляFoo
конструктора России непосредственно в файл класса, где эти аннотации позднее читаются API-интерфейсом отражения.Использовать неиспользуемые / устаревшие инструкции байт-кода
Поскольку другие назвали это, я включу это также. Java была ранее использование подпрограмм по
JSR
иRET
отчетности. JBC даже знал свой собственный тип обратного адреса для этой цели. Однако использование подпрограмм усложнило статический анализ кода, поэтому эти инструкции больше не используются. Вместо этого компилятор Java будет дублировать код, который он компилирует. Тем не менее, это в основном создает идентичную логику, поэтому я на самом деле не считаю это достижением чего-то другого. Точно так же вы можете, например, добавитьNOOP
инструкция байтового кода, которая также не используется компилятором Java, но это также не позволит вам достичь чего-то нового. Как указано в контексте, эти упомянутые «инструкции по функциям» теперь удалены из набора допустимых кодов операций, что делает их еще менее функциональными.источник
<clinit>
метода, определяя методы с именем,<clinit>
но принимая параметры или имеяvoid
невозвратный тип. Но эти методы не очень полезны, JVM будет их игнорировать, а байт-код не сможет их вызвать. Единственное использование было бы, чтобы запутать читателей.IllegalMonitorStateException
если вы пропустилиmonitorexit
инструкцию. И в случае исключительного метода выхода, который не смог сделать этоmonitorexit
, он сбрасывает монитор без вывода сообщений.Вот некоторые функции, которые могут быть реализованы в байт-коде Java, но не в исходном коде Java:
Выдача проверенного исключения из метода без объявления того, что метод его выбрасывает. Проверенные и непроверенные исключения - это то, что проверяется только компилятором Java, а не JVM. Из-за этого, например, Scala может генерировать проверенные исключения из методов, не объявляя их. Тем не менее, с помощью дженериков Java есть обходной путь, называемый хитрым броском .
Наличие в классе двух методов, которые отличаются только типом возвращаемого значения, как уже упоминалось в ответе Йоахима : спецификация языка Java не допускает двух методов в одном классе, если они различаются только по типу возвращаемого значения (т. Е. Одно и то же имя, один и тот же список аргументов, ...). Спецификация JVM, однако, не имеет такого ограничения, поэтому файл класса может содержать два таких метода, просто нет способа создать такой файл класса, используя обычный компилятор Java. В этом ответе есть хороший пример / объяснение .
источник
Thread.stop(Throwable)
для подлого броска. Я предполагаю, что тот, который уже связан, быстрее, хотя.GOTO
может использоваться с метками для создания собственных управляющих структур (кромеfor
while
etc)this
локальную переменную внутри методаВ качестве связанного момента вы можете получить имя параметра для методов, если оно скомпилировано с отладкой ( Paranamer делает это, читая байт-код
источник
override
эта локальная переменная?this
Переменная имеет нулевой индекс, но помимо того , что предварительно инициализируетсяthis
ссылкой при вводе метода экземпляра, это просто локальная переменная. Таким образом, вы можете записать в него другое значение, которое может действовать как завершениеthis
или изменениеthis
переменной, в зависимости от того, как вы ее используете.this
может быть переназначено? Я думаю, что это было просто слово переопределить, что заставило меня задуматься, что именно это означает.Может быть, раздел 7А в этом документе представляет интерес, хотя речь идет о подводных камнях, а не о функциях байт-кода .
источник
В языке Java первое утверждение в конструкторе должно быть вызовом конструктора суперкласса. Байт-код не имеет этого ограничения, вместо этого правило состоит в том, что конструктор суперкласса или другой конструктор в том же классе должен вызываться для объекта перед доступом к членам. Это должно позволить больше свободы, таких как:
Я не проверял это, поэтому, пожалуйста, поправьте меня, если я ошибаюсь.
источник
То, что вы можете делать с байтовым кодом, а не с простым Java-кодом, - это генерировать код, который можно загружать и запускать без компилятора. Многие системы имеют JRE, а не JDK, и если вы хотите генерировать код динамически, может быть лучше, если не проще, генерировать байт-код вместо кода Java, прежде чем его можно будет использовать.
источник
Я написал оптимизатор байт-кода, когда был I-Play (он был разработан для уменьшения размера кода для приложений J2ME). Одной из функций, которую я добавил, была возможность использовать встроенный байт-код (аналогично встроенному языку ассемблера в C ++). Мне удалось уменьшить размер функции, которая была частью библиотечного метода, с помощью инструкции DUP, поскольку мне нужно значение в два раза. У меня также были нулевые байтовые инструкции (если вы вызываете метод, который принимает символ, и вы хотите передать int, который, как вы знаете, не нужно приводить, я добавил int2char (var) для замены char (var), и он удалил бы Инструкция i2c для уменьшения размера кода. Я также сделал это с плавающей точкой a = 2.3; с плавающей точкой b = 3.4; с плавающей точкой c = a + b; и это будет преобразовано в фиксированную точку (быстрее, а некоторые J2ME этого не сделали) поддержка плавающей запятой).
источник
В Java, если вы пытаетесь переопределить открытый метод защищенным методом (или любым другим ограничением доступа), вы получаете ошибку: «попытка назначить более слабые права доступа». Если вы делаете это с помощью байт-кода JVM, то с верификатором все в порядке, и вы можете вызывать эти методы через родительский класс, как если бы они были публичными.
источник