Недоступные в языке возможности байткода Java


Оглавление (нажмите, чтобы открыть):

Байт-код

Все языки можно условно разделить на компилируемые и интерпретируемые. В Java используется третий подход — байт-код. Исходный код Java преобразуется компилятором в байт-код (а не машинный код). A байт-код Java преобразуется в машинный код с помощью специального интерпретатора, называемого виртуальной машиной Java (Java Virtual Machine — JVM).

Рассмотрим более детально как работает Java:

  1. Создается исходный документ (исходник) – файл c расширением .java.
  2. Исходник пропускается через компилятор, который проверяет код на ошибки и выдает конечный результат.
  3. Компилятор создает новый документ, закодированный с помощью байт-кода. Любое устройство, способное выполнять Java, сможет интерпретировать этот файл в такой формат, который сможет запустить. Скомпилированный байт-код не зависит от платформы.
  4. Виртуальная машина считывает и выполняет байт-код.

Литература: Head First Java, 2nd Edition. Глава 1 — Как работает Java.

Виртуальная Java-машина, байт-код, JIT-компиляция. Категории программ, написанных на языке Java

Первоначально слово “программа” означало последовательность инструкций процессора для решения какой-либо задачи. Эти инструкции являлись машинными кодами, и разницы между исходным и исполняемым кодом программы не было. Разница появилась, когда программы стали писать на языках программирования. При этом программой стали называть как текст, содержащийся в файле с исходным кодом, так и исполняемый файл.

Для устранения неоднозначности термина “программа”, исполняемый код программы принято называть приложением (application). Термин “приложение” – сокращение от фразы “приложение операционной системы”. Он означает, что исполняемый код программы может работать только под управлением соответствующей операционной системы. Работа под управлением операционной системы позволяет избежать зависимости программы от устройства конкретного варианта аппаратуры на компьютере, где она должна выполняться. Например, как автору программы, так и пользователю совершенно безразлично, как устроено устройство, с которого считывается информация – будет ли это жёсткий диск с одной, двумя или шестнадцатью считывающих головок. Или это будет CD-привод, DVD-привод или ещё какой-либо другой тип носителя. Но переносимость обычных приложений ограничивается одним типом операционных систем. Например, приложение MS Windows ® не будет работать под Linux, и наоборот. Программы, написанные на языке Java, выполняются под управлением специальной программы -виртуальной Java-машины, и поэтому обладают переносимостью на любую операционную систему, где имеется соответствующая Java-машина. Благодаря этому они являются не приложениями какой-либо операционной системы, а приложениями Java.

Программы, написанные на языке Java, представляют из себя наборы классов и сохраняются в текстовых файлах с расширением .java. (Про то, что такое классы, будет рассказано несколько позже). При компиляции текст программы переводится (транслируются) в двоичные файлы с расширением .class. Такие файлы содержат байт-код — инструкции для абстрактного Java-процессора в виде байтовых последовательностей команд этого процессора и данных к ним. Для того, чтобы байт-код был выполнен на каком-либо компьютере, он должен быть переведён в инструкции для соответствующего процессора. Именно этим и занимается Java-машина. Первоначально байт-код всегда интерпретировался: каждый раз, как встречалась какая-либо инструкция Java-процессора, она переводилась в последовательность инструкций процессора компьютера. Естественно, это значительно замедляло работу приложений Java.

В настоящее время используется более сложная схема, называемая JIT-компиляцией (Jast-In-Time) – компиляцией “по ходу дела”, “налету”. Когда какая-либо инструкция (или набор инструкций) Java-процессора выполняется в первый раз, происходит компиляция соответствующего ей байт-кода с сохранением скомпилированного кода в специальном буфере. При последующем вызове той же инструкции вместо её интерпретации происходит вызов из буфера скомпилированного кода. Поэтому интерпретация происходит только при первом вызове инструкции.

Сложные оптимизирующие JIT-компиляторы действуют ещё изощрённей. Поскольку обычно компиляция инструкции идёт гораздо дольше по сравнению с интерпретацией этой инструкции, время её выполнения в первый раз при наличии JIT-компиляции может заметно отличаться в худшую сторону по сравнению с чистой интерпретацией. Поэтому бывает выгоднее сначала запустить процесс интерпретации, а параллельно ему в фоновом режиме компилировать инструкцию. Только после окончания процесса компиляции при последующих вызовах инструкции будет исполняться её скомпилированный код. – До этого все её вызовы будут интерпретироваться. Разработанная Sun виртуальная машина HotSpot осуществляет JIT-компиляцию только тех участков байт-кода, которые критичны к времени выполнения программы. При этом по ходу работы программы происходит оптимизация скомпилированного кода.

Благодаря компиляции программ Java в платформонезависимый байт-код обеспечивается переносимость этих программ не только на уровне исходного кода, но и на уровне скомпилированных приложений. Конечно, при этом на компьютере, где выполняется приложение, должна быть установлена программа виртуальной Java-машины (Java Virtual Machine — JVM), скомпилированная в коды соответствующего процессора (native code – “родной” код). На одном и том же компьютере может быть установлено несколько Java-машин разных версий или от разных производителей. Спецификация Java-машины является открытой, точно так же, как требования к компилятору языка Java. Поэтому различные фирмы, а не только Sun, разрабатывают компиляторы Java и Java-машины.

Приложение операционной системы запускается с помощью средств операционной системы. Приложение Java, напротив, запускается с помощью виртуальной Java-машины, которая сама является приложением операционной системы. Таким образом, сначала стартует Java-машина. Она получает в качестве параметра имя файла с компилированным кодом класса. В этом классе ищется и запускается на выполнение подпрограмма с именем main.

Приложения Java обладают не только хорошей переносимостью, но и высокой скоростью работы. Однако даже при наличии JIT-компиляции они всё-таки могут выполняться медленнее, чем программы, написанные на C или C++. Это связано с тем, что JIT-компиляция создаёт не такой оптимальный код как многопроходный компилятор C/C++, который может тратить очень большое время и много ресурсов на отыскивание конструкций программы, которые можно оптимизировать. А JIT-компиляция происходит “на лету”, в условиях жёсткой ограниченности времени и ресурсов. Для решения этой проблемы были разработаны компиляторы программ Java в код конкретных программно-аппаратных платформ (native code – “родной” код). Например, свободно распространяемый фондом GNU компилятор gjc. Правда, заметные успехи Sun в усовершенствовании Java-машины позволили практически достичь, а в ряде случаев даже обогнать по быстродействию программы, написанные на других языках. В частности, приложения Java, активно занимающиеся выделением-высвобождением памяти, работают быстрее своих аналогов, написанных на C/C++, благодаря специальному механизму программных слотов памяти (slot – “паз, отверстие для вставления чего-либо”).

Виртуальная Java-машина не только исполняет байт-код (интерпретирует его, занимается JIT-компиляцией и исполняет JIT-компилированный код), но и выполняет ряд других функций. Например, взаимодействует с операционной системой, обеспечивая доступ к файлам или поддержку графики. А также обеспечивает автоматическое высвобождение памяти, занятой ненужными объектами – так называемую сборку мусора (garbage collection).

Программы Java можно разделить на несколько основных категорий:

· Приложение (application) – аналог “обычной” прикладной программы.

· Апплет (applet) – специализированная программа с ограниченными возможностями, работающая в окне WWW-документа под управлением браузера.

· Сервлет (servlet) — специализированная программа с ограниченными возможностями, работающая в WWW на стороне сервера. Используется преимущественно в рамках технологии JSP (Java Server Pages — Серверных Страниц Java) для программирования WWW-документов со стороны сервера.

· Серверное приложение (Enterprise application) – предназначено для многократного использования на стороне сервера.

· Библиотека (Java Class Library – библиотека классов, либо NetBeans Module – модуль платформы NetBeans) – предназначена для многократного использования программами Java

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

Сервлеты – это приложения Java , запускаемые со стороны сервера. Они имеют возможности доступа к файловой системе и другим ресурсам сервера через набор управляющих конструкций, предопределённых в рамках технологии JSP и пакета javax.servlet. Технология JSP заключается в наличии дополнительных конструкций в HTML- или XML-документах, которые позволяют осуществлять вызовы сценариев (“скриптов”), написанных на языке Java. В результате удаётся очень просто и удобно осуществлять обработку данных или элементов документа, и внедрять в нужные места документа результаты обработки. Сценарии Java перед первым выполнением автоматически компилируются на сторо не сервера, поэтому выполняемый код выполняется достаточно быстро. Но, конечно, требует, чтобы была установлена соответствующая Java-машина. Например, входящая в состав Sun Application Server – программного обеспечения, обеспечивающего поддержку большого количества необходимых серверных возможностей для работы в WWW. Отметим, что Sun Application Server также распространяется бесплатно и входит в комплект NetBeans Enterprise Pack.

Первоначально Java позиционировался Sun как язык, обеспечивающий развитые графические возможности WWW-документов благодаря включению в них апплетов. Однако в настоящее время основными областями использования Java является прикладное программирование на основе приложений, страниц JSP и сервлетов, а также других видов серверных программ. При этом использование апплетов играет незначительную роль.

Виртуальную Java-машину часто называют исполняющей средой (Java Runtime Environment — JRE).

Существует два основных способа установки Java-машины на клиентский компьютер:

· JRE из поставки Software Development Kit (SDK) — Комплекта разработки программного обеспечения.

· Специализированный вариант JRE в составе Интернет-браузера, называющийся Java plugin.

Комплект последних версий SDK можно свободно загружать с сайта Sun https://java.sun.com/ .

При использовании апплетов требуется, чтобы в состав браузера входил специализированный комплект JRE. Как правило, он поставляется вместе с браузером, и может при необходимости обновляться. Для MS Internet Explorer такой комплект и его обновления могут быть свободно загружены с сайта Microsoft.

Имеется возможность установки Java-машины от различных производителей, не обязательно устанавливать комплект SDK от Sun. На одном и том же компьютере может быть установлено сразу несколько различных Java-машин, в том числе комплекты SDK разных версий. Правда, опыт показывает, что при этом некоторые программы, написанные на Java, теряют работоспособность (частично или полностью).

Комплекты SDK имеют классификацию, опирающуюся на версию Java (языка программирования и, соответственно, Java-машины) и тип создаваемых приложений. Так, ко времени написания данного текста выходили версии SDK 1.0, 1.1, 1.2, 1.3, 1.4, 1.5 и 1.6. У каждой версии имеется ряд подверсий, не сопровождающихся изменением языка программирования, а связанных в основном с исправлением ошибок или внесением небольших изменений в библиотеки. Например, 1.4.1_01 или 1.5.0_04.

Версии Java 1.0 и 1.1 принято называть Java 1. Все версии Java начиная c 1.2 называют Java 2. Однако более надёжно классифицировать по номеру SDK, так как язык Java для версии SDK 1.5 очень заметно отличается по возможностям от языка Java для более ранних версий SDK – в него добавлено большое количество новых синтаксических конструкций, а также изменён ряд правил. Поэтому код, правильный в Java для версии SDK 1.5, может оказаться неправильным в Java для версии SDK 1.4. Не говоря уж про Java для версии SDK 1.3 или 1.2. Кроме того, недавно компания Sun перестала использовать в названиях комплектов программного обеспечения термин Java 2 и происходящие от него сокращения вида j2.

Комплекты разработки SDK одной версии отличаются по типу создаваемых с их помощью приложений. Имеется три типа SDK:

· Java ME – комплект Java Micro Edition (микро-издание) https://java.sun.com/j2me/, предназначенный для программирования “тонких аппаратных клиентов”. То есть устройств, обладающих малыми ресурсами — наладонных компьютеров, сотовых телефонов, микроконтроллеров, смарт-карт. Старое название J2ME.

· Java SE – комплект Java Standard Edition (стандартное издание) https://java.sun.com/j2se/, предназначенный для программирования “толстых клиентов”. То есть устройств, обладающих достаточно большими ресурсами — обычных компьютеров. Старое название J2SE.

· Java EE– комплект Java Enterprise Edition https://java.sun.com/j2ee/, предназначенный для написания серверного программного обеспечения. Старое название J2EE.

При распространении какого-либо продукта, написанного на Java, возможна установка только программного обеспечения Java-машины (JRE – Java Runtime Environment). Например, в случае использования Java 1.4.1_01 — комплекта j2re1.4.1_01. При этом создаётся папка с именем j2re1.4.1_01 с вложенными папками bin и lib. В папке bin содержатся файлы и папки, необходимые для работы Java-машины и дополнительных инструментов для работы с ней в специальных режимах. В папке lib содержатся вспомогательные файлы и библиотеки, в основном связанные с параметрами настроек системы.

Также возможна установка целиком SDK. Например, при установке SDK Java SE 1.5.0_04 создаётся папка JDK1.5.0_04 с вложенными папками bin, demo, include, jre, lib, sample, а также архивом src.zip с исходными кодами стандартных классов Java. В папке bin содержатся файлы инструментов разработки, в папке demo — файлы примеров с исходными кодами. В папке include — заголовки файлов C для доступа к ряду библиотек Java и отладчику виртуальной Java-машины на платформо-зависимом уровне — на основе интерфейсов JNI (Java Native Interface) и JVMDI (Java Virtual Machine Debugging Interface), соответственно. В папке jre находятся файлы, необходимые для работы с виртуальной Java-машиной. Папка lib содержит ряд библиотек и сопроводительных файлов, необходимых для работы инструментов из папки bin. В папке sample находятся примеры с исходными кодами.

Аббревиатура JDK расшифровывается как Java Development Kit – комплект разработки программного обеспечения на Java. К сожалению, в комплекте отсутствует даже самая простейшая документация с описанием назначения имеющихся в нём инструментов – даны ссылки на сайт компании Sun, где можно найти эту информацию. Поэтому перечислим назначение основных инструментов. Они делятся на несколько категорий.

Дата добавления: 2020-02-28 ; просмотров: 1111 | Нарушение авторских прав

Вопрос по jvm, cross-platform, java &#8211 Независимость от платформы в байт-коде Java

Я иногда задаюсь вопросом, почему Java упоминается как независимый от платформы язык?

Я не мог найти правильное объяснение нижеприведенных пунктов:

Является ли JVM одинаковой для Windows / Linux / Mac OS?Байт-код генерируется одинаково для одного и того же класса в указанных выше средах?

Если ответ на вышеуказанные вопросы НЕТ, то как достигается независимость платформы.

Пожалуйста, помогите мне в изучении этой основной концепции.

Не за что. Компилятор одинаков для всех платформ. Но, так как этозапускаемый файлсам файл будет другим, то есть в Windows, это будет .exe, в Linux, это будет исполняемый файл Linux и т. д.

Байт-код генерируется одинаково для одного и того же класса в указанных выше средах?

Да. Вот почему JavaСОВЕРШЕННО ОДИН РАЗ. Бежать в любом месте.

+1, но вы еще не сказали, что .class, созданный компилятором, одинаков на всех платформах и полностью определен JLS (docs.oracle.com/javase/specs/jls/se7/html/index.html) и JVMS (docs.oracle.com/javase/specs/jvms/se7/html/index.html)

+1 за объяснение и полезную ссылку

@ Aubin Хороший вопрос. Но если я начну писать . нужно упомянуть множество моментов. Вот почему я добавил ссылку. В этой ссылке ваша точка зрения находится во втором абзаце 🙂

большое спасибо всем вам

Компилятор Java (javac) скомпилирует исходный код в байт-код (хранится в файле .class)

Байт-код Java (.class) не зависит от ОС, он имеет одинаковое расширение во всех разных ОС. Но так как это не является специфичным для любой ОС или другой среды, никто не может запустить это (если нет машины, чей собственный набор команд является байт-кодами, то есть они могут понимать сам байт-код)

JVM загрузить и выполнить байт-код

Виртуальная машина (ВМ) является программной реализацией машины (то есть компьютера), которая выполняет программы подобно физической машине. Java также имеет виртуальную машину под названием Java Virtual Machine (JVM).

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


исполнительный механизм должен изменить байт-код на язык, который может быть выполнен машиной в JVM.Это включает в себя различные задачи, такие как поиск узких мест в производительности и перекомпиляция (в собственный код) часто используемых разделов кода. Байт-код может быть изменен на подходящий язык одним из двух способов,

Переводчик: Читает, интерпретирует и выполняет инструкции байт-кода одну за другой

JIT (Just-In-Time) компилятор: Компилятор JIT был введен, чтобы компенсировать недостатки интерпретатора. Механизм выполнения сначала запускается как интерпретатор, и в соответствующее время JIT-компилятор компилирует весь байт-код, чтобы изменить его на собственный код. После этого механизм выполнения больше не интерпретирует метод, а непосредственно выполняет с использованием собственного кода. Выполнение в нативном коде намного быстрее, чем интерпретация инструкций одна за другой. Скомпилированный код может быть выполнен быстро, так как собственный код хранится в кэше.

Таким образом, в итоге коды Java будут скомпилированы в байт-код, который не зависит от платформы, а Java имеет виртуальную машину (JVM), специфичную для каждой из разных платформ (операционных систем и т. Д.), Которая может загружать и интерпретировать эти байт-коды для машинно-специфического кода.

Функции Bytecode недоступны на языке Java

jvm (8)

Есть ли в настоящее время (Java 6) вещи, которые вы можете сделать в байт-коде Java, которые невозможно выполнить с языка Java?

Я знаю, что оба Тьюринга завершены, поэтому читайте «можно», поскольку «может делать значительно быстрее / лучше, или просто по-другому».

Я думаю о дополнительных байт- invokedynamic , таких как invokedynamic , которые не могут быть сгенерированы с использованием Java, за исключением того, что конкретный для будущей версии.

После долгой работы с байтовым кодом Java и проведя некоторые дополнительные исследования по этому вопросу, я кратко изложил свои выводы:

Выполнить код в конструкторе перед вызовом супер-конструктора или вспомогательного конструктора

На языке программирования Java (JPL) первый оператор конструктора должен быть вызовом супер конструктора или другого конструктора того же класса. Это не относится к байт-коду Java (JBC). Внутри байтового кода абсолютно законно выполнять любой код перед конструктором, если:

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

Задайте поля экземпляра перед вызовом супер-конструктора или вспомогательного конструктора

Как упоминалось ранее, совершенно законно устанавливать значение поля экземпляра перед вызовом другого конструктора. Там даже существует устаревший хак, который позволяет использовать эту «функцию» в версиях Java до 6:

Таким образом, поле может быть установлено перед вызовом суперструктора, который, однако, невозможен. В JBC это поведение может быть реализовано.

Вставить вызов супер-конструктора

В Java невозможно определить вызов конструктора типа

До Java 7u23 верификатор VM HotSpot пропустил эту проверку, и именно поэтому это было возможно. Это использовалось несколькими инструментами генерации кода как своего рода взломом, но более не законно реализовать такой класс.

Последнее было всего лишь ошибкой в ​​этой версии компилятора. В новых версиях компилятора это снова возможно.

Определить класс без какого-либо конструктора

Компилятор Java всегда будет реализовывать хотя бы один конструктор для любого класса. В байт-коде Java это не требуется. Это позволяет создавать классы, которые не могут быть построены даже при использовании отражения. Однако использование sun.misc.Unsafe прежнему позволяет создавать такие экземпляры.

Определить методы с одинаковой подписью, но с другим типом возврата

В JPL метод идентифицируется как уникальный по его имени и его необработанным типам параметров. В JBC дополнительно рассматривается тип необработанного возврата.

Определить поля, которые не отличаются по имени, но только по типу

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

Выбрасывать необъявленные проверенные исключения, не заражая их

Время выполнения Java и байт-код Java не знают о концепции проверенных исключений. Только компилятор Java проверяет, что проверенные исключения всегда либо пойманы, либо объявлены, если они были выбраны.

Использовать динамический вызов метода за пределами лямбда-выражений

Так называемый динамический вызов метода может использоваться для чего угодно, а не только для лямбда-выражений Java. Использование этой функции позволяет, например, отключить логику выполнения во время выполнения. Многие языки динамического программирования, которые сводятся к JBC, улучшили свою производительность , используя эту инструкцию. В байт-коде Java вы также можете эмулировать лямбда-выражения в Java 7, где компилятор еще не разрешил использовать динамический вызов метода, в то время как JVM уже понимал инструкцию.

Использовать идентификаторы, которые обычно не считаются законными

Когда-либо воображалось использование пробелов и разрыв строки в имени вашего метода? Создайте свой собственный JBC и удачи для обзора кода. Единственными недопустимыми символами для идентификаторов являются . , ; , [ и / . Кроме того, методы, не названные или не могут содержать и > .

Переназначить final параметры или this ссылка

final параметры не существуют в JBC и, следовательно, могут быть переназначены. Любой параметр, включая this ссылку, сохраняется только в простом массиве в JVM, что позволяет переназначить this ссылку в индексе 0 в рамках одного кадра метода.

Переназначить final поля

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

Для static final полей даже разрешено переназначать поля вне инициализатора класса.

Обработать конструкторы и инициализатор класса, как если бы они были методами

Это скорее концептуальная функция, но конструкторы не обрабатываются по-разному в JBC, чем обычные методы. Только верификатор JVM гарантирует, что конструкторы вызовут другой законный конструктор. Помимо этого, это просто соглашение об именах Java, что конструкторы должны быть вызваны и что инициализатор класса называется . Помимо этой разницы, представление методов и конструкторов идентично. Как отметил Хольгер в комментарии, вы даже можете определить конструкторы с возвращаемыми типами, отличными от void или инициализатором класса с аргументами, хотя эти методы невозможно назвать.

Вызовите любой супер метод (до Java 1.1)

Однако это возможно только для версий 1 и 1.1 Java. В 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 операции INVOKESPECIAL который напрямую связывает вызов метода bar в Foo::foo с версией Foo . Этот код операции обычно используется для реализации суперпользователей, но вы можете повторно использовать код операции для реализации описанного поведения.

Аннотации мелкозернистого типа

В 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 требует выполнения нескольких условий, чтобы разрешить вызов метода по умолчанию:

  1. Метод должен быть наиболее конкретным (не должен быть переопределен вспомогательным интерфейсом, который реализуется любым типом, включая супер типы).
  2. Тип интерфейса по умолчанию должен быть реализован непосредственно классом, который вызывает метод по умолчанию. Однако, если интерфейс B расширяет интерфейс A но не отменяет метод в A , метод все равно может быть вызван.

Для байтового кода Java учитывается только второе условие. Первый, однако, не имеет значения.

Вызов супер-метода для экземпляра, который не является

Компилятор Java только позволяет вызывать метод супер (или интерфейс по умолчанию) для экземпляров this . В байтовом коде также можно вызвать метод super для экземпляра того же типа, который аналогичен следующему:

Доступ к синтетическим элементам

В байт-коде Java можно напрямую получить доступ к синтетическим элементам. Например, рассмотрим, как в следующем примере обращается внешний экземпляр другого экземпляра Bar :

Это обычно справедливо для любого синтетического поля, класса или метода.

Определить информацию типа несинхронизированного типа

Хотя среда выполнения Java не обрабатывает общие типы (после того, как компилятор Java применяет стирание типа), эта информация по-прежнему привязывается к скомпилированному классу как метаинформация и становится доступной через API отражения.

Верификатор не проверяет согласованность этих метаданных String -encoded values. Поэтому можно определить информацию об общих типах, которые не соответствуют стиранию. В качестве позывной могут выполняться следующие утверждения:

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

Добавить метаинформацию параметра только для определенных методов

Компилятор Java позволяет вставлять имя параметра и информацию о модификаторе при компиляции класса с включенным флажком parameter . В формате файла класса Java эта информация сохраняется в каждом методе, что позволяет только внедрять такую ​​информацию метода для определенных методов.

Повесьте вещи и сильно свалите свою JVM

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

Аннотировать тип приемника конструктора, когда нет внешнего класса

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

Поскольку Foo.class.getDeclaredConstructor().getAnnotatedReceiverType() , однако, возвращает объект AnnotatedType представляющий Foo , можно включить аннотации типов для конструктора Foo непосредственно в файл класса, где эти аннотации позже считываются API-интерфейсом отражения.

Использовать неиспользуемые / устаревшие инструкции кода байта

Поскольку другие назвали его, я также включу его. Ранее Java использовала подпрограммы с помощью инструкций JSR и RET . JBC даже знал свой тип обратного адреса для этой цели. Однако использование подпрограмм делало слишком сложным статический анализ кода, поэтому эти инструкции больше не используются. Вместо этого компилятор Java будет дублировать код, который он компилирует. Тем не менее, это в основном создает идентичную логику, и поэтому я не считаю ее достижением чего-то другого. Аналогичным образом, вы можете, например, добавить инструкцию байтового кода NOOP которая не используется компилятором Java, но это не позволило бы вам добиться чего-то нового. Как указано в контексте, эти упомянутые «функциональные инструкции» теперь удаляются из набора юридических кодов операций, что делает их еще менее характерными.

Может быть, раздел 7A в этом документе представляет интерес, хотя речь идет об ошибках байт-кода, а не о байт- кодах .

  • GOTO можно использовать с ярлыками для создания собственных структур управления ( for исключением того, for while т. Д.),
  • Вы можете переопределить this локальную переменную внутри метода
  • Объединив оба из них, вы можете создать оптимизированный байт-код для создания хвоста (я делаю это в JCompilo )

В качестве связанной точки вы можете получить имя параметра для методов, если скомпилировано с помощью debug ( Paranamer делает это, читая байт-код

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

Однако некоторые функции, которые не производятся современными компиляторами Java, однако:

Это флаг, который может быть установлен в классе, и указывает, как обрабатывается конкретный угловой случай invokespecial байт-кода для этого класса. Он задается всеми современными компиляторами Java (где «modern» is> = Java 1.1, если я правильно помню), и только древние компиляторы Java создавали файлы классов, где это не было установлено. Этот флаг существует только для соображений обратной совместимости. Обратите внимание, что, начиная с Java 7u51, ACC_SUPER полностью игнорируется из-за соображений безопасности.

Байт-коды jsr / ret .

Эти байт-коды использовались для реализации подпрограмм (в основном для реализации блоков finally ). Они больше не производятся с Java 6 . Причиной их устаревания является то, что они усложняют статическую проверку для большого выигрыша (т. Е. Используемый код почти всегда может быть реализован с помощью обычных переходов с очень небольшими накладными расходами).

Имеет два метода в классе, которые отличаются только типом возврата.

Спецификация языка Java не позволяет использовать два метода в одном классе, если они отличаются только их возвращаемым типом (то же имя, тот же список аргументов, . ). Однако спецификация JVM не имеет такого ограничения, поэтому файл класса может содержать два таких метода, просто невозможно создать такой файл класса, используя обычный компилятор Java. В этом ответе есть хороший пример / объяснение.

В Java, если вы попытаетесь переопределить общедоступный метод с помощью защищенного метода (или любого другого сокращения доступа), вы получите сообщение об ошибке: «попытка назначить более слабые права доступа». Если вы делаете это с помощью байт-кода JVM, верификатор в порядке с ним, и вы можете вызвать эти методы через родительский класс, как если бы они были общедоступными.

В языке Java первый оператор в конструкторе должен быть вызовом конструктора суперкласса. Байт-код не имеет этого ограничения, вместо этого правило заключается в том, что конструктор супер-класса или другой конструктор в том же классе должен быть вызван для объекта перед доступом к членам. Это должно позволить больше свободы, такой как:

  • Создайте экземпляр другого объекта, сохраните его в локальной переменной (или стеке) и передайте его как параметр суперклассу, сохраняя при этом ссылку в этой переменной для другого использования.
  • Вызовите другие конструкторы на основе условия. Это должно быть возможным: как условно вызывать другой конструктор на Java?

Я не тестировал их, поэтому, пожалуйста, поправьте меня, если я ошибаюсь.

Вот некоторые функции, которые можно выполнить в байт-коде Java, но не в исходном коде Java:

Выбрасывание проверенного исключения из метода, не объявляя, что метод выбрасывает его. Проверенные и непроверенные исключения — это вещь, которая проверяется только компилятором Java, а не JVM. Из-за этого, например, Scala может выдавать проверенные исключения из методов, не объявляя их. Хотя с Java-дженериками есть обходной путь, называемый « подлый бросок» .

Имея два метода в классе, которые отличаются только в обратном типе, как уже упоминалось в ответе Йоахима : Спецификация языка Java не позволяет использовать два метода в одном классе, когда они отличаются только их возвращаемым типом (то же имя, тот же список аргументов, . ). Однако спецификация JVM не имеет такого ограничения, поэтому файл класса может содержать два таких метода, просто невозможно создать такой файл класса, используя обычный компилятор Java. В этом ответе есть хороший пример / объяснение.

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

Недоступные в языке возможности байткода Java

Байт-код — или байткод (англ. byte code), иногда также используется термин псевдокод машинно независимый код низкого уровня, генерируемый транслятором и исполняемый интерпретатором. Большинство инструкций байт кода эквивалентны одной или… … Википедия

байт-код — Машинно независимый код, генерируемый Java компилятором. [ГОСТ Р 54456 2011] Тематики телевидение, радиовещание, видео EN byte codeJava byte code … Справочник технического переводчика

Java Native Interface — (JNI) стандартный механизм для запуска кода, под управлением виртуальной машины Java (JVM), который написан на языках С/С++ или Ассемблера, и скомпонован в виде динамических библиотек, позволяет не использовать статическое связывание. Это… … Википедия

Java Virtual Machine — В этой статье не хватает ссылок на источники информации. Информация должна быть проверяема, иначе она может быть поставлена под сомнение и удалена. Вы можете … Википедия

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

Java — разработанная компанией Sun Microsystems система программирования, содержащая: универсальный язык программирования Java; компилятор; и виртуальную машину Java s Virtual Machine (JVM). Компилятор транслирует исходный Java текст в байт код, который … Финансовый словарь

Java — Иное название этого понятия «Ява»; см. также другие значения. Не следует путать с JavaScript. Java Класс языка … Википедия

Java (программная платформа) — Не следует путать с JavaScript. Программная платформа Java ряд программных продуктов и спецификаций компании Sun Microsystems, ранее независимой компании, а ныне дочерней компании корпорации Oracle, которые совместно предоставляют систему для… … Википедия


Код операции — Эта статья об инструкциях; о системе команд в целом см.: Машинный код. Код операции, операционный код, опкод часть машинного языка, называемая инструкцией и определяющая операцию, которая должна быть выполнена. Определение и формат кодов… … Википедия

Код операции (информатика) — Эта статья об инструкциях; о системе команд в целом см.: Машинный код. В комьютерной отрасли под кодом операции (также операционный код, опкод англ. operation code) понимают часть машинного языка, называемую инструкцией, определяющую операцию,… … Википедия

Часть 2. Обзор библиотеки BCEL и других инструментов для изменения байт-кода

Серия контента:

Этот контент является частью # из серии # статей: Модификация байт-кода Java VM

Этот контент является частью серии: Модификация байт-кода Java VM

Следите за выходом новых статей этой серии.

В open-source сообществе существует известное утверждение: «для любой задачи существует несколько способов решения и, следовательно, несколько инструментов, позволяющих реализовать эти решения». Поэтому не стоит ограничиваться единственным средством модификации байт-кода JVM — библиотекой ASM из предыдущей статьи. Для полноты картины необходимо рассмотреть и альтернативные варианты.

1. Библиотека BCEL

BCEL (Byte Code Engineering Library) API представляет собой комплекс программных средств для статического анализа и динамического изменения и создания содержимого class-файлов Java. Артефакты библиотеки BCEL и необходимую документацию можно загрузить с официального сайта https://jakarta.apache.org/bcel/ или найти в соответствующих репозиториях различных дистрибутивов LINUX.

Главным преимуществом библиотеки BCEL является высокоуровневый API, который избавляет от необходимости изучать внутреннюю структуру class-файлов и особенности реализации байт-кода (от этого недостатка страдает библиотека ASM).

1.1. Краткий обзор библиотеки BCEL

Ключевыми компонентами библиотеки BCEL являются:

  • пакет org.apache.bcel.classfile с классами, описывающими элементы class-файла, но не предназначенными для его изменения. Классы этого пакета могут применяться для считывания байт-кода из class-файла и последующей записи байт-кода в файл. При отсутствии исходного кода такая возможность может оказаться полезной для анализа существующих классов. В классе JavaClass содержится функциональность для обращения к методам, полям и атрибутам анализируемого класса;
  • пакет org.apache.bcel.generic с классами для динамического изменения или генерации объектов типа JavaClass или Method с функциональностью для вставки требуемых фрагментов непосредственно в байт-код или удаления ненужной информации из class-файла;
  • пакет org.apache.bcel.util с уже готовыми классами для просмотра class-файлов или преобразования их в различные форматы.

1.2. JavaClass

Важнейший класс JavaClass находится в пакете org.apache.bcel.classfile и представляет детальное отображение структуры class-файла. Этот класс отражает структуру class-файла с высоким уровнем абстракции и поэтому содержит информацию о полях, методах, ссылки на родительский класс (супер-класс) и интерфейсы, реализуемые исследуемым классом. Объекты типа JavaClass создаются классом ClassParser, выполняющим анализ содержимого class-файлов.

Внутри класса JavaClass важную роль играет объект ConstantPool (блок констант). В этом объекте содержится массив элементов типа Constant фиксированного размера, которые можно извлечь с помощью метода getConstant() по заданному индексу. В объектах типа Method и Field хранится сигнатура, определяющая тип данных для полей или типы входных и выходных параметров для методов.

1.3. Схема использования библиотеки BCEL

Работу по анализу содержимого class-файла можно значительно упростить, если прибегнуть к помощи имеющихся в BCEL классов. Класс Repository позволяет за одно действие выполнить считывание содержимого class-файла в объект типа JavaClass:

После этого можно выполнить анализ байт-кода, полученного из класса. Доступ к внутренним компонентам осуществляется через «классические» getter- и setter-методы. В листинге 1 приведен код, выводящий высокоуровневую информацию о структуре класса:

Листинг 1. Просмотр информации о классе

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

1.4. Пример анализа содержимого class-файла

В листинге 2 приведен класс Prim, который в дальнейшем будет анализироваться при помощи классов библиотеки BCEL:

Листинг 2. Класс Prim

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

Листинг 3. Программа для вывода информации о Java-классе.

Программу из листинга 3 можно запустить, написав в командной строке:

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

1.5. Модификация скомпилированного кода

Разобрав, как в BCEL выполняется анализ содержимого class-файла, можно перейти к более сложной задаче — изменению байт-кода скомпилированного класса. В ходе работы с классом Prim (по сценарию для него отсутствует исходный код и документация) обнаружилось, что в нём отсутствует конструктор по умолчанию. Этот недостаток также можно исправить с помощью BCEL, как показано в листинге 4.

Листинг 4. Программа для добавления в Java-класс конструктора по умолчанию

В отличие от класса для вывода информации из листинга 3, в классе из листинга 4 объявлены два поля. Поле originCode имеет тип JavaClass и хранит исходный байт-код модифицируемого класса. Поле modifiedCode предназначено для измененного байт-кода, поэтому для него используется тип ClassGen из пакета org.apache.bcel.generic, предоставляющий функциональность для добавления полей, методов и атрибутов.

Ссылка на байт-код, считанный из модифицируемого class-файла, сразу же передаётся в поле modifiedCode. После выполнения необходимых проверок в методе main добавляется новый конструктор с уровнем доступа public. При использовании BCEL модификаторы классов задаются в виде битовой маски, и, чтобы установить для метода main набор модификаторов public static final, потребовалось бы передать следующее значение:

Измененный байт-код записывается в файл new_prim.class, после чего можно проверить в нём наличие нового конструктора по умолчанию. Таким же способом можно изменять class-файлы, добавляя и удаляя поля и методы.

2. Другие инструменты для модификации байт-кода JVM

Список инструментов и утилит для анализа и изменения байт-кода виртуальной машины Java не ограничивается описанными выше библиотеками ASM и BCEL: также внимания заслуживают и некоторые альтернативные методики из области Bytecode Engineering.

2.1. Javassist

Библиотека классов Javassist позволяет считывать и записывать байт-код виртуальной машины Java, используя для этого возможности Reflections API (Reflections API является компонентом платформы JSE, дополнительную информацию можно найти в документации к пакету java.lang.reflect). Операции с байт-кодом выполняются по следующему алгоритму: требуемый класс извлекается из пула классов JVM в виде объекта типа CtClass, затем в байт-код этого объекта вносятся изменения, после чего модифицированный байт-код сохраняется в class-файле при помощи методов writeFile или toBytecode, как показано в листинге 5:

Листинг 5. Использование библиотеки Javassist

В Javassist имеется возможность вставки байт-кода в конкретное место в классе, указывая позицию относительно текущего метода (объект типа CtMethod) или конструктора (объект типа CtConstructor) модифицируемого класса:

и замены заданных фрагментов кода:

Для выполнения последнего действия предлагается класс ExprEditor, позволяющий выборочно заменять части кода в теле модифицируемого метода.

2.2. Serp

В библиотеку Serp (https://serp.sourceforge.net/) входит набор высокоуровневых API для работы с любыми элементами байт-кода: от структур классов, полей и методов до отдельных инструкций в коде методов. Эта библиотека не обладает никакими отличительными особенностями по сравнению с рассмотренными решениями.

2.3. JOIE

Утилита JOIE (https://www.cs.duke.edu/ari/joie/) — ещё один инструмент для «безопасной трансформации байт-кода Java». Особенность этого пакета в том, что функциональность разделена на два уровня: низкоуровневый интерфейс позволяет выполнять операции на уровне отдельных инструкций байт-кода, а высокоуровневый интерфейс позволяет работать со структурой классов, добавлять поля, методы, новые интерфейсы. Главную задачу авторы инструмента видят в обеспечении максимальной безопасности при изменении байт-кода с сохранением удобства применения инструмента.

2.4. Jikes Bytecode Toolkit (IBM)

Jikes Bytecode Toolkit (https://www.alphaworks.ibm.com/tech/jikesbt/) — это библиотека Java-классов, разработанная компанией IBM для выполнения операций чтения/записи скомпилированных class-файлов и создания новых class-файлов. В настоящее время является компонентом платформы Eclipse (Eclipse Concern Manipulation Environment (CME)).

Заключение

В предыдущей статье рассматривались основы процесса модификации байт-кода виртуальной машины Java и одно из инструментальных средств для выполнения модификации — библиотека ASM. В данной статье, завершающей серию «Модификация байт-кода Java VM», подробно рассмотрен еще один инструмент для модификации байт-кода — библиотека BCEL, и приведен краткий обзор других инструментов подобного рода.

особенности байт-кода не доступны на языке Java

November 2020

55.7k раз

Есть ли в настоящее время (Java 6) вещи, которые вы можете сделать в Java байт-код, который вы не можете сделать внутри языка Java?

Я знаю, что оба Тьюринга, так что читать «может сделать» как «можно сделать значительно быстрее / лучше, или просто в другой путь».

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

12 ответы


В Java языке первый оператор в конструкторе должен быть вызов конструктора суперкласса. Bytecode не имеют такого ограничения, а не правилом является то, что конструктор суперкласса или другой конструктор в том же классе должен быть вызван для объекта перед обращением к членам. Это должно позволить больше свободы, такие как:

  • Создание экземпляра другого объекта, хранить его в локальной переменной (или стек) и передать его в качестве параметра супер конструктор класса в то же время сохраняя ссылку на эту переменную для другого использования.
  • Вызов различных других конструкторов на основе состояния. Это должно быть возможным: Как вызвать другой конструктор условно в Java?

Я не проверял это, поэтому, пожалуйста, поправьте меня, если я ошибаюсь.

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

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

Есть некоторые особенности, которые не производятся с помощью современных Java компиляторов, однако:

Это флаг , который может быть установлен на классе и определяет , как конкретный угол случай invokespecial байткод обрабатывается для этого класса. Он устанавливается всеми современными Java компиляторы (где «современный» является> = Java 1.1, если я правильно помню) , и только древние Java компиляторы произвел файлы классов , где это было не-сет. Этот флаг существует только по причинам обратной совместимости. Обратите внимание , что начиная с Java 7u51, ACC_SUPER полностью игнорируется из — за соображений безопасности.

В jsr / ret байткоды.

Эти байткоды были использованы для реализации подпрограмм ( в основном для реализации finally блоков). Они не будут больше не производятся , так как Java 6 . Причина их устаревания является то , что они усложняют статические проверки много для не большого коэффициента усиления (то есть код , который использует почти всегда можно повторно реализовать с нормальными скачками с минимальными накладными расходами).

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

Спецификация Java языка не допускает два метода в том же классе , когда они отличаются только в их типе возвращаемого значения (т.е. же имя, тот же список аргументов, . ). Спецификация JVM , однако, не имеет такого ограничения, поэтому класс файл может содержать два таких методов, нет никакого способа , чтобы произвести такой файл класса , используя обычный Java компилятор. Там хороший пример / объяснение в этом ответе .

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

Есть некоторые особенности, которые не производятся с помощью современных Java компиляторов, однако:

Это флаг , который может быть установлен на классе и определяет , как конкретный угол случай invokespecial байткод обрабатывается для этого класса. Он устанавливается всеми современными Java компиляторы (где «современный» является> = Java 1.1, если я правильно помню) , и только древние Java компиляторы произвел файлы классов , где это было не-сет. Этот флаг существует только по причинам обратной совместимости. Обратите внимание , что начиная с Java 7u51, ACC_SUPER полностью игнорируется из — за соображений безопасности.

В jsr / ret байткоды.

Эти байткоды были использованы для реализации подпрограмм ( в основном для реализации finally блоков). Они не будут больше не производятся , так как Java 6 . Причина их устаревания является то , что они усложняют статические проверки много для не большого коэффициента усиления (то есть код , который использует почти всегда можно повторно реализовать с нормальными скачками с минимальными накладными расходами).

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

Спецификация Java языка не допускает два метода в том же классе , когда они отличаются только в их типе возвращаемого значения (т.е. же имя, тот же список аргументов, . ). Спецификация JVM , однако, не имеет такого ограничения, поэтому класс файл может содержать два таких методов, нет никакого способа , чтобы произвести такой файл класса , используя обычный Java компилятор. Там хороший пример / объяснение в этом ответе .

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

Есть некоторые особенности, которые не производятся с помощью современных Java компиляторов, однако:

Это флаг , который может быть установлен на классе и определяет , как конкретный угол случай invokespecial байткод обрабатывается для этого класса. Он устанавливается всеми современными Java компиляторы (где «современный» является> = Java 1.1, если я правильно помню) , и только древние Java компиляторы произвел файлы классов , где это было не-сет. Этот флаг существует только по причинам обратной совместимости. Обратите внимание , что начиная с Java 7u51, ACC_SUPER полностью игнорируется из — за соображений безопасности.

Мастер Йода рекомендует:  Оплата за просмотр – новый тренд в ценообразовании

В jsr / ret байткоды.

Эти байткоды были использованы для реализации подпрограмм ( в основном для реализации finally блоков). Они не будут больше не производятся , так как Java 6 . Причина их устаревания является то , что они усложняют статические проверки много для не большого коэффициента усиления (то есть код , который использует почти всегда можно повторно реализовать с нормальными скачками с минимальными накладными расходами).

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

Спецификация Java языка не допускает два метода в том же классе , когда они отличаются только в их типе возвращаемого значения (т.е. же имя, тот же список аргументов, . ). Спецификация JVM , однако, не имеет такого ограничения, поэтому класс файл может содержать два таких методов, нет никакого способа , чтобы произвести такой файл класса , используя обычный Java компилятор. Там хороший пример / объяснение в этом ответе .

Я написал байткодовый оптимизатор, когда я был I-Play (он был разработан, чтобы уменьшить размер кода для приложений J2ME). Одна особенность, которую я добавил была возможность использовать встроенный байткод (по аналогии с инлайн ассемблера в C ++). Мне удалось уменьшить размер функции, которая была частью библиотечного метода путем использования DUP инструкции, так как мне нужно значение в два раза. Я также имел нулевые инструкции байт (если вы вызываете метод, который принимает символ, и вы хотите передать Int, что вы знаете, не нужно быть отлит я добавил int2char (вар), чтобы заменить символ (Var), и было бы удалить . инструкция i2c, чтобы уменьшить размер кода, который я также сделал это сделать плавать = 2,3; поплавок Ъ = 3,4, поплавок с = а + Ь, и которые будут преобразованы в фиксированной точке (быстрее, а также некоторые J2ME не сделал поддержка операций с плавающей точкой).

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

Есть некоторые особенности, которые не производятся с помощью современных Java компиляторов, однако:

Это флаг , который может быть установлен на классе и определяет , как конкретный угол случай invokespecial байткод обрабатывается для этого класса. Он устанавливается всеми современными Java компиляторы (где «современный» является> = Java 1.1, если я правильно помню) , и только древние Java компиляторы произвел файлы классов , где это было не-сет. Этот флаг существует только по причинам обратной совместимости. Обратите внимание , что начиная с Java 7u51, ACC_SUPER полностью игнорируется из — за соображений безопасности.

В jsr / ret байткоды.

Эти байткоды были использованы для реализации подпрограмм ( в основном для реализации finally блоков). Они не будут больше не производятся , так как Java 6 . Причина их устаревания является то , что они усложняют статические проверки много для не большого коэффициента усиления (то есть код , который использует почти всегда можно повторно реализовать с нормальными скачками с минимальными накладными расходами).

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

Спецификация Java языка не допускает два метода в том же классе , когда они отличаются только в их типе возвращаемого значения (т.е. же имя, тот же список аргументов, . ). Спецификация JVM , однако, не имеет такого ограничения, поэтому класс файл может содержать два таких методов, нет никакого способа , чтобы произвести такой файл класса , используя обычный Java компилятор. Там хороший пример / объяснение в этом ответе .

После работы с байт-код Java, довольно долгое время и делать некоторые дополнительные исследования по этому вопросу, вот краткое изложение моих выводов:

Выполнение кода в конструкторе до вызова супер конструктор или вспомогательный конструктор

В языке программирования Java (JPL), первый оператор конструктора должен быть вызов супер конструктор или другой конструктор того же класса. Это не верно для байт-код Java (JBC). В байт-код, это абсолютно законно выполнить любой код перед конструктором, до тех пор, как:

  • Другой совместимый конструктор вызывается через некоторое время после этого блока кода.
  • Этот вызов не в условном операторе.
  • До этого вызова конструктора, ни одного поля построенного экземпляра не читается и ни один из его методов не вызывается. Это означает, следующий пункт.

Набор полей экземпляра до вызова супер конструктор или вспомогательный конструктор

Как упоминалось ранее, это совершенно законно, чтобы установить значение поля экземпляра перед вызовом другого конструктора. Там даже существует наследие хака, который делает его способным использовать эту «особенность» в Java версий до 6:

Таким образом, поле может быть установлено до того, как супер конструктор вызывается, который, однако, не более возможным. В JBC, такое поведение еще может быть реализовано.

Branch супер вызов конструктора

В Java, это не представляется возможным определить вызов конструктора, как

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

Последняя была просто ошибка в этой версии компилятора. В новых версиях компилятора, это опять возможно.

Определение класса без конструктора

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

Определение методов с одинаковой сигнатурой, но с другим типом возвращаемого значения

В JPL, метод идентифицируется уникальным по его имени и его типов сырья параметров. В JBC, исходный тип возвращаемого дополнительно рассматривается.

Определение полей, которые не отличаются по имени, но только по типу

Файл класса может содержать несколько полей одного и того же имени, пока они объявляют другой тип поля. JVM всегда относится к полю как кортеж из имени и типа.

Бросьте необъявленные проверенные исключения, не ловя их

Среда выполнения Java и байт-код Java не знают о концепции проверяемых исключений. Это только Java компилятор, который проверяет, что проверили исключения всегда либо пойманы или объявлены, если они выбрасываются.

Использование динамического вызова метода вне лямбда-выражений

Так называемый динамический вызов метода может быть использована для чего, не только для лямбда — выражений Java. Использование этой функции позволяет, например, переключаться из логики выполнения во время выполнения. Многие динамические языки программирования , которые сводятся к JBC улучшили свои показатели с помощью этой инструкции. В байт — код Java, можно также эмулировать лямбда — выражения в Java 7 , где компилятор еще не позволяет для любого использования динамического вызова метода в то время как JVM уже поняли инструкцию.

Используйте идентификаторы, которые обычно не считаются законным

Всегда казалось , используя пробелы и разрыв строки в названии вашего метода? Создайте свой собственный JBC и удачи для проверки коды. Единственные недопустимые символы для идентификаторов . , ; , [ и / . Кроме того, методы, которые не названы или не может содержать и > .

Переназначьте final параметры или this ссылки


final параметры не существуют в JBC и , следовательно , могут быть переназначены. Любой параметр, в том числе this ссылки хранится только в простом массиве в пределах виртуальной машины Java , что позволяет переназначить this ссылку на индексе в 0 пределах одного кадра метода.

Переназначение final поля

Пока окончательное поле присваивается в конструкторе, это законно передать это значение или даже не присвоить значение вообще. Таким образом, следующие два конструкторы законны:

Для static final полей, он даже позволил переназначить поля за пределами инициализатора класса.

Лечить Конструкторы и класс инициализатор, как если бы они были методы

Это больше концептуальной функция , но конструкторы не обрабатываются по -другому в JBC , чем обычные методы. Это только удостоверяющая JVM, которая гарантирует , что конструкторы называют другой правовой конструктор. Кроме того, это просто Java именования , что конструкторы должны называться и что класс инициализатор называется . Помимо этого различия, представление методов и конструкторов идентично. Как Хольгер отметил в комментарии, можно даже определить конструкторы с другими , чем типы возвращаемых void или класс инициализатора с аргументами, даже если это не представляется возможным назвать эти методы.

Вызов любого супер метода (до 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. Этот опкод обычно используется для реализации супер вызовов методов , но вы можете использовать опкод для реализации описанного поведения.

аннотации типов мелкозернистого

In Java, annotations are applied according to their @Target that the annotations declares. Using byte code manipulation, it is possible to define annotations independently of this control. Also, it is for example possible to annotate a parameter type without annotating the parameter even if the @Target annotation applies to both elements.

Define any attribute for a type or its members

Within the Java language, it is only possible to define annotations for fields, methods or classes. In JBC, you can basically embed any information into the Java classes. In order to make use of this information, you can however no longer rely on the Java class loading mechanism but you need to extract the meta information by yourself.

Overflow and implicitly assign byte , short , char and boolean values

The latter primitive types are not normally known in JBC but are only defined for array types or for field and method descriptors. Within byte code instructions, all of the named types take the space 32 bit which allows to represent them as int . Officially, only the int , float , long and double types exist within byte code which all need explicit conversion by the rule of the JVM’s verifier.

Not release a monitor

A synchronized block is actually made up of two statements, one to acquire and one to release a monitor. In JBC, you can acquire one without releasing it.

Note: In recent implementations of HotSpot, this instead leads to an IllegalMonitorStateException at the end of a method or to an implicit release if the method is terminated by an exception itself.

Добавьте больше чем один return заявление типа инициализаторе

В Java, даже тривиальный инициализатор типа, такие как

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

Создание неприводимых петель

Компилятор Java преобразует циклы для GOTO операторов в байт-код Java. Такие заявления могут быть использованы для создания неприводимых петель, которые никогда не делает компилятор Java.

Определить рекурсивный блок улова

В байт-код Java, вы можете определить блок:

Аналогичное утверждение создается неявно при использовании synchronized блока в Java , где любое исключение, высвобождая монитор возвращается к инструкции для освобождения этого монитора. Как правило, исключение не должно происходить на такой инструкции , но если бы это (например, устаревший ThreadDeath ), монитор все равно будет освобожден.

Вызов метода любой по умолчанию

Компилятор Java требует несколько условий, которые должны выполняться для того, чтобы обеспечить вызов метода по умолчанию в:

  1. Метод должен быть наиболее специфичный один (не должен быть переопределен суб — интерфейс , который реализуется с помощью любого типа, включая типы супер).
  2. Тип интерфейса по умолчанию метод должен быть реализован непосредственно классом , который вызывает метод по умолчанию. Однако, если интерфейс B расширяет интерфейс , A но не переопределить метод в A метод все еще может быть вызван.

Для байт-код Java, только второе условие имеет значение. Первый из них, однако, не имеет значения.

Призовите супер метод на примере, который не является this

Компилятор Java позволяет только для вызова метода супер (или интерфейс по умолчанию) на экземплярах this . В байт — код, это , однако , также можно вызвать супер метод на примере того же типа , подобную следующей:

Доступ синтетические члены

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

Как правило, это верно для любого синтетического поля, класса или метода.

Определите вне синхронизации информации общего типа

В то время как среда выполнения Java не обрабатывает общего типа (после того, как компилятор Java применяется тип стирания), эта информация до сих пор attcheched в скомпилированный класс как мета-информации и доступны через API отражения.

Испытатель не проверяет согласованность этих мета — данных String закодирована значения. Таким образом , можно определить информацию о родовых типах , которые не соответствуют стиранию. Как concequence следующие assertings может быть правдой:

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

Добавление параметра метаинформации только для определенных методов

Компилятор Java позволяет встраивание имени параметра и информации модификатора при компиляции класса с parameter поддержкой флага. В формате файл класса Java, эта информация хранится , однако за метод , что позволяет только внедрить такую информацию метода для некоторых методов.

Натворить и жесткий крах вашей виртуальной машины Java

В качестве примера, в байт — код Java, вы можете определить , чтобы вызвать любой метод любого типа. Как правило, верификатор будет жаловаться , если тип не известен таким способом. Однако, если вы вызываете неизвестный метод на массив, я нашел ошибку в некоторой версии виртуальной машины Java , где верификатор пропустит это и ваша JVM добьет когда команда вызывается. Едва ли это особенность , хотя, но это технически то , что не представляется возможным с JAVAC скомпилированный Java. Java имеет какой — то двойной проверки. Первая проверка применяется компилятором Java, вторым по виртуальной машине Java , когда загружен класс. Пропуская компилятор, вы можете обнаружить слабое место в проверке на верификаторе. Это скорее общее утверждение , чем функции, хотя.

Комментирование типа приемника конструктора, когда нет внешнего класса

Так как Java 8, не-статические методы и конструкторы внутренних классов могут объявить тип приемника и аннотировать эти типы. Конструкторы классов верхнего уровня не могут комментировать их тип приемника, как они больше всего не объявлять один.

С Foo.class.getDeclaredConstructor().getAnnotatedReceiverType() тем не менее возвращать AnnotatedType представляющее Foo , можно включить аннотацию типа для Foo «конструктора непосредственно в файле класс , где эти аннотации позже прочитаны отражение API.

Используйте инструкции байт-код не используется / унаследованных

Так как другие назвали его, я буду включать его. Java была ранее использование подпрограмм по JSR и RET отчетности. JBC даже знал , что его собственный тип обратного адреса для этой цели. Однако, использование подпрограмм сделали усложнять статический анализ кода , который является , почему эти инструкции не больше использоваться. Вместо этого компилятор Java будет дублировать код он компилирует. Тем не менее, это в основном создает одинаковую логику, поэтому я не считаю , что добиться чего — то другого. Кроме того , вы могли бы, например , добавить NOOP инструкция байт-код, который не используется компилятором Java либо, но это не будет действительно позволит вам достичь что-то новое либо. Как указано в контексте, эта упомянутая «инструкция особенности» теперь удаляется из набора юридических опкодов который не делает их еще меньше функции.

особенности байт-кода не доступны на языке Java

Есть ли в настоящее время (Java 6) вещи, которые вы можете сделать в Java байт-код, который вы не можете сделать внутри языка Java?

Я знаю, что оба Тьюринга, так что читать «может сделать» как «можно сделать значительно быстрее / лучше, или просто в другой путь».

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

После работы с байт-код Java, довольно долгое время и делать некоторые дополнительные исследования по этому вопросу, вот краткое изложение моих выводов:

Выполнение кода в конструкторе до вызова супер конструктор или вспомогательный конструктор

В языке программирования Java (JPL), первый оператор конструктора должен быть вызов супер конструктор или другой конструктор того же класса. Это не верно для байт-код Java (JBC). В байт-код, это абсолютно законно выполнить любой код перед конструктором, до тех пор, как:


  • Другой совместимый конструктор вызывается через некоторое время после этого блока кода.
  • Этот вызов не в условном операторе.
  • До этого вызова конструктора, ни одного поля построенного экземпляра не читается и ни один из его методов не вызывается. Это означает, следующий пункт.

Набор полей экземпляра до вызова супер конструктор или вспомогательный конструктор

Как упоминалось ранее, это совершенно законно, чтобы установить значение поля экземпляра перед вызовом другого конструктора. Там даже существует наследие хака, который делает его способным использовать эту «особенность» в Java версий до 6:

Таким образом, поле может быть установлено до того, как супер конструктор вызывается, который, однако, не более возможным. В JBC, такое поведение еще может быть реализовано.

Branch супер вызов конструктора

В Java, это не представляется возможным определить вызов конструктора, как

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

Последняя была просто ошибка в этой версии компилятора. В новых версиях компилятора, это опять возможно.

Определение класса без конструктора

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

Определение методов с одинаковой сигнатурой, но с другим типом возвращаемого значения

В JPL, метод идентифицируется уникальным по его имени и его типов сырья параметров. В JBC, исходный тип возвращаемого дополнительно рассматривается.

Определение полей, которые не отличаются по имени, но только по типу

Файл класса может содержать несколько полей одного и того же имени, пока они объявляют другой тип поля. JVM всегда относится к полю как кортеж из имени и типа.

Бросьте необъявленные проверенные исключения, не ловя их

Среда выполнения Java и байт-код Java не знают о концепции проверяемых исключений. Это только Java компилятор, который проверяет, что проверили исключения всегда либо пойманы или объявлены, если они выбрасываются.

Использование динамического вызова метода вне лямбда-выражений

Так называемый динамический вызов метода может быть использована для чего, не только для лямбда — выражений Java. Использование этой функции позволяет, например, переключаться из логики выполнения во время выполнения. Многие динамические языки программирования , которые сводятся к JBC улучшили свои показатели с помощью этой инструкции. В байт — код Java, можно также эмулировать лямбда — выражения в Java 7 , где компилятор еще не позволяет для любого использования динамического вызова метода в то время как JVM уже поняли инструкцию.

Используйте идентификаторы, которые обычно не считаются законным

Всегда казалось , используя пробелы и разрыв строки в названии вашего метода? Создайте свой собственный JBC и удачи для проверки коды. Единственные недопустимые символы для идентификаторов . , ; , [ и / . Кроме того, методы, которые не названы или не может содержать и > .

Переназначьте final параметры или this ссылки

final параметры не существуют в JBC и , следовательно , могут быть переназначены. Любой параметр, в том числе this ссылки хранится только в простом массиве в пределах виртуальной машины Java , что позволяет переназначить this ссылку на индексе в 0 пределах одного кадра метода.

Переназначение final поля

Пока окончательное поле присваивается в конструкторе, это законно передать это значение или даже не присвоить значение вообще. Таким образом, следующие два конструкторы законны:

Для static final полей, он даже позволил переназначить поля за пределами инициализатора класса.

Лечить Конструкторы и класс инициализатор, как если бы они были методы

Это больше концептуальной функция , но конструкторы не обрабатываются по -другому в JBC , чем обычные методы. Это только удостоверяющая JVM, которая гарантирует , что конструкторы называют другой правовой конструктор. Кроме того, это просто Java именования , что конструкторы должны называться и что класс инициализатор называется . Помимо этого различия, представление методов и конструкторов идентично. Как Хольгер отметил в комментарии, можно даже определить конструкторы с другими , чем типы возвращаемых void или класс инициализатора с аргументами, даже если это не представляется возможным назвать эти методы.

Вызов любого супер метода (до 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, даже тривиальный инициализатор типа, такие как

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

Мастер Йода рекомендует:  Эффект буквицы

Создание неприводимых петель

Компилятор Java преобразует циклы для GOTO операторов в байт-код Java. Такие заявления могут быть использованы для создания неприводимых петель, которые никогда не делает компилятор Java.

Определить рекурсивный блок улова

В байт-код Java, вы можете определить блок:

Аналогичное утверждение создается неявно при использовании synchronized блока в Java , где любое исключение, высвобождая монитор возвращается к инструкции для освобождения этого монитора. Как правило, исключение не должно происходить на такой инструкции , но если бы это (например, устаревший ThreadDeath ), монитор все равно будет освобожден.

Вызов метода любой по умолчанию

Компилятор Java требует несколько условий, которые должны выполняться для того, чтобы обеспечить вызов метода по умолчанию в:

  1. Метод должен быть наиболее специфичный один (не должен быть переопределен суб — интерфейс , который реализуется с помощью любого типа, включая типы супер).
  2. Тип интерфейса по умолчанию метод должен быть реализован непосредственно классом , который вызывает метод по умолчанию. Однако, если интерфейс B расширяет интерфейс , A но не переопределить метод в A метод все еще может быть вызван.

Для байт-код Java, только второе условие имеет значение. Первый из них, однако, не имеет значения.

Призовите супер метод на примере, который не является this

Компилятор Java позволяет только для вызова метода супер (или интерфейс по умолчанию) на экземплярах this . В байт — код, это , однако , также можно вызвать супер метод на примере того же типа , подобную следующей:

Доступ синтетические члены


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

Как правило, это верно для любого синтетического поля, класса или метода.

Определите вне синхронизации информации общего типа

В то время как среда выполнения Java не обрабатывает общего типа (после того, как компилятор Java применяется тип стирания), эта информация до сих пор attcheched в скомпилированный класс как мета-информации и доступны через API отражения.

Испытатель не проверяет согласованность этих мета — данных String закодирована значения. Таким образом , можно определить информацию о родовых типах , которые не соответствуют стиранию. Как concequence следующие assertings может быть правдой:

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

Добавление параметра метаинформации только для определенных методов

Компилятор Java позволяет встраивание имени параметра и информации модификатора при компиляции класса с parameter поддержкой флага. В формате файл класса Java, эта информация хранится , однако за метод , что позволяет только внедрить такую информацию метода для некоторых методов.

Натворить и жесткий крах вашей виртуальной машины Java

В качестве примера, в байт — код Java, вы можете определить , чтобы вызвать любой метод любого типа. Как правило, верификатор будет жаловаться , если тип не известен таким способом. Однако, если вы вызываете неизвестный метод на массив, я нашел ошибку в некоторой версии виртуальной машины Java , где верификатор пропустит это и ваша JVM добьет когда команда вызывается. Едва ли это особенность , хотя, но это технически то , что не представляется возможным с JAVAC скомпилированный Java. Java имеет какой — то двойной проверки. Первая проверка применяется компилятором Java, вторым по виртуальной машине Java , когда загружен класс. Пропуская компилятор, вы можете обнаружить слабое место в проверке на верификаторе. Это скорее общее утверждение , чем функции, хотя.

Комментирование типа приемника конструктора, когда нет внешнего класса

Так как Java 8, не-статические методы и конструкторы внутренних классов могут объявить тип приемника и аннотировать эти типы. Конструкторы классов верхнего уровня не могут комментировать их тип приемника, как они больше всего не объявлять один.

С Foo.class.getDeclaredConstructor().getAnnotatedReceiverType() тем не менее возвращать AnnotatedType представляющее Foo , можно включить аннотацию типа для Foo «конструктора непосредственно в файле класс , где эти аннотации позже прочитаны отражение API.

Используйте инструкции байт-код не используется / унаследованных

Так как другие назвали его, я буду включать его. Java была ранее использование подпрограмм по JSR и RET отчетности. JBC даже знал , что его собственный тип обратного адреса для этой цели. Однако, использование подпрограмм сделали усложнять статический анализ кода , который является , почему эти инструкции не больше использоваться. Вместо этого компилятор Java будет дублировать код он компилирует. Тем не менее, это в основном создает одинаковую логику, поэтому я не считаю , что добиться чего — то другого. Точно так же, можно, например , добавить NOOP команду байт — код , который не используется компилятором Java либо , но это не будет действительно позволит вам достичь что — то новое либо. Как указано в контексте, эта упомянутая «инструкция особенности» теперь удаляется из набора юридических опкодов который не делает их еще меньше функции.

Байт-код Java: чудеса или реальность?

Основная особенность Java, которая позволяет решать описанные ранее про­блемы обеспечения безопасности и переносимости программ, состоит в том, что компилятор Java выдает не исполняемый код, а так называемый байт-код в выс­шей степени оптимизированный набор инструкций, предназначенных для выполнения в исполняющей системе Java, называемой виртуальной машиной Java (Java Virtual Machine — JVM). Собственно говоря, первоначальная версия виртуальной машины JVM разрабатывалась в качестве интерпретатора байт-кода. Это может вызывать недоумение, поскольку для обеспечения максимальной производитель­ности компиляторы многих современных языков программирования призваны создавать исполняемый код. Но то, что программа на Java интерпретируется вир­туальной машиной JVM, как раз помогает решить основные проблемы разработки программ для Интернета. И вот почему.

Трансляция программы Java в байт-код значительно упрощает ее выполнение в разнотипных средах, поскольку на каждой платформе необходимо реализовать только виртуальную машину JVM. Если в отдельной системе имеется исполняю­щий пакет, в ней можно выполнять любую программу на Java. Следует, однако, иметь в виду, что все виртуальные машины JVM на разных платформах, несмотря на некоторые отличия и особенности их реализации, способны правильно ин­терпретировать один и тот же байт-код. Если бы программа на Java компилирова­лась в машинозависимый код, то для каждого типа процессоров, подключенных к Интернету, должны были бы существовать отдельные версии одной и той же программы. Ясно, что такое решение неприемлемо. Таким образом, организация выполнения байт-кода виртуальной машиной JVM — простейший способ создания по-настоящему переносимых программ.

Тот факт, что программа на Java выполняется виртуальной машиной JVM, спо­собствует также повышению ее безопасности. Виртуальная машина JVM управля­ет выполнением программы, поэтому она может изолировать программу и воспре­пятствовать возникновению побочных эффектов от ее выполнения за пределами данной системы. Как станет ясно в дальнейшем, ряд ограничений, существующих в языке Java, также способствует повышению безопасности.

В общем, когда программа компилируется в промежуточную форму, а затем ин­терпретируется виртуальной машиной JVM, она выполняется медленнее, чем если бы она была скомпилирована в исполняемый код. Но в Java это отличие в произ­водительности не слишком заметно. Байт-код существенно оптимизирован, и по­этому его применение позволяет виртуальной машине JVM выполнять программы значительно быстрее, чем следовало ожидать.

Язык Java был задуман как интерпретируемый, но ничто не препятствует ему оперативно выполнять компиляцию байт-кода в машинозависимый код для повы­шения производительности. Поэтому вскоре после выпуска Java появилась техно­логия HotSpot, которая предоставляет динамический компилятор (или так называ­емый JIT-компилятор) байт-кода. Если динамический компилятор входит в состав виртуальной машины JVM, то избранные фрагменты байт-кода компилируются в исполняемый код по частям, в реальном времени и по требованию. Важно по­нимать, что одновременная компиляция всей программы Java в исполняемый код нецелесообразна, пocкoлькyJava производит различные проверки, которые могут быть сделаны только во время выполнения. Вместо этого динамический компи­лятор компилирует код во время выполнения по мере надобности. Более того, компилируются не все фрагменты байт-кода, а только те, которым компиляция принесет выгоду, а остальной код просто интерпретируется. Тем не менее прин­цип динамической компиляции обеспечивает значительное повышение произ­водительности. Даже при динамической компиляции байт-кода характеристики переносимости и безопасности сохраняются, поскольку виртуальная машина JVM по-прежнему отвечает за целостность исполняющей среды.

Функции Bytecode недоступны на языке Java

jvm (8)

Есть ли в настоящее время (Java 6) вещи, которые вы можете сделать в байт-коде Java, которые невозможно выполнить с языка Java?

Я знаю, что оба Тьюринга завершены, поэтому читайте «можно», поскольку «может делать значительно быстрее / лучше, или просто по-другому».

Я думаю о дополнительных байт- invokedynamic , таких как invokedynamic , которые не могут быть сгенерированы с использованием Java, за исключением того, что конкретный для будущей версии.

После долгой работы с байтовым кодом Java и проведя некоторые дополнительные исследования по этому вопросу, я кратко изложил свои выводы:

Выполнить код в конструкторе перед вызовом супер-конструктора или вспомогательного конструктора

На языке программирования Java (JPL) первый оператор конструктора должен быть вызовом супер конструктора или другого конструктора того же класса. Это не относится к байт-коду Java (JBC). Внутри байтового кода абсолютно законно выполнять любой код перед конструктором, если:

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

Задайте поля экземпляра перед вызовом супер-конструктора или вспомогательного конструктора

Как упоминалось ранее, совершенно законно устанавливать значение поля экземпляра перед вызовом другого конструктора. Там даже существует устаревший хак, который позволяет использовать эту «функцию» в версиях Java до 6:

Таким образом, поле может быть установлено перед вызовом суперструктора, который, однако, невозможен. В JBC это поведение может быть реализовано.

Вставить вызов супер-конструктора

В Java невозможно определить вызов конструктора типа

До Java 7u23 верификатор VM HotSpot пропустил эту проверку, и именно поэтому это было возможно. Это использовалось несколькими инструментами генерации кода как своего рода взломом, но более не законно реализовать такой класс.

Последнее было всего лишь ошибкой в ​​этой версии компилятора. В новых версиях компилятора это снова возможно.

Определить класс без какого-либо конструктора

Компилятор Java всегда будет реализовывать хотя бы один конструктор для любого класса. В байт-коде Java это не требуется. Это позволяет создавать классы, которые не могут быть построены даже при использовании отражения. Однако использование sun.misc.Unsafe прежнему позволяет создавать такие экземпляры.

Определить методы с одинаковой подписью, но с другим типом возврата

В JPL метод идентифицируется как уникальный по его имени и его необработанным типам параметров. В JBC дополнительно рассматривается тип необработанного возврата.

Определить поля, которые не отличаются по имени, но только по типу

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

Выбрасывать необъявленные проверенные исключения, не заражая их

Время выполнения Java и байт-код Java не знают о концепции проверенных исключений. Только компилятор Java проверяет, что проверенные исключения всегда либо пойманы, либо объявлены, если они были выбраны.

Использовать динамический вызов метода за пределами лямбда-выражений

Так называемый динамический вызов метода может использоваться для чего угодно, а не только для лямбда-выражений Java. Использование этой функции позволяет, например, отключить логику выполнения во время выполнения. Многие языки динамического программирования, которые сводятся к JBC, улучшили свою производительность , используя эту инструкцию. В байт-коде Java вы также можете эмулировать лямбда-выражения в Java 7, где компилятор еще не разрешил использовать динамический вызов метода, в то время как JVM уже понимал инструкцию.

Использовать идентификаторы, которые обычно не считаются законными

Когда-либо воображалось использование пробелов и разрыв строки в имени вашего метода? Создайте свой собственный JBC и удачи для обзора кода. Единственными недопустимыми символами для идентификаторов являются . , ; , [ и / . Кроме того, методы, не названные или не могут содержать и > .

Переназначить final параметры или this ссылка

final параметры не существуют в JBC и, следовательно, могут быть переназначены. Любой параметр, включая this ссылку, сохраняется только в простом массиве в JVM, что позволяет переназначить this ссылку в индексе 0 в рамках одного кадра метода.

Переназначить final поля

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

Для static final полей даже разрешено переназначать поля вне инициализатора класса.

Обработать конструкторы и инициализатор класса, как если бы они были методами

Это скорее концептуальная функция, но конструкторы не обрабатываются по-разному в JBC, чем обычные методы. Только верификатор JVM гарантирует, что конструкторы вызовут другой законный конструктор. Помимо этого, это просто соглашение об именах Java, что конструкторы должны быть вызваны и что инициализатор класса называется . Помимо этой разницы, представление методов и конструкторов идентично. Как отметил Хольгер в комментарии, вы даже можете определить конструкторы с возвращаемыми типами, отличными от void или инициализатором класса с аргументами, хотя эти методы невозможно назвать.

Вызовите любой супер метод (до Java 1.1)

Однако это возможно только для версий 1 и 1.1 Java. В 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 операции INVOKESPECIAL который напрямую связывает вызов метода bar в Foo::foo с версией Foo . Этот код операции обычно используется для реализации суперпользователей, но вы можете повторно использовать код операции для реализации описанного поведения.

Аннотации мелкозернистого типа

В 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 требует выполнения нескольких условий, чтобы разрешить вызов метода по умолчанию:

  1. Метод должен быть наиболее конкретным (не должен быть переопределен вспомогательным интерфейсом, который реализуется любым типом, включая супер типы).
  2. Тип интерфейса по умолчанию должен быть реализован непосредственно классом, который вызывает метод по умолчанию. Однако, если интерфейс B расширяет интерфейс A но не отменяет метод в A , метод все равно может быть вызван.

Для байтового кода Java учитывается только второе условие. Первый, однако, не имеет значения.

Вызов супер-метода для экземпляра, который не является

Компилятор Java только позволяет вызывать метод супер (или интерфейс по умолчанию) для экземпляров this . В байтовом коде также можно вызвать метод super для экземпляра того же типа, который аналогичен следующему:

Доступ к синтетическим элементам

В байт-коде Java можно напрямую получить доступ к синтетическим элементам. Например, рассмотрим, как в следующем примере обращается внешний экземпляр другого экземпляра Bar :

Это обычно справедливо для любого синтетического поля, класса или метода.

Определить информацию типа несинхронизированного типа

Хотя среда выполнения Java не обрабатывает общие типы (после того, как компилятор Java применяет стирание типа), эта информация по-прежнему привязывается к скомпилированному классу как метаинформация и становится доступной через API отражения.

Верификатор не проверяет согласованность этих метаданных String -encoded values. Поэтому можно определить информацию об общих типах, которые не соответствуют стиранию. В качестве позывной могут выполняться следующие утверждения:

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

Добавить метаинформацию параметра только для определенных методов

Компилятор Java позволяет вставлять имя параметра и информацию о модификаторе при компиляции класса с включенным флажком parameter . В формате файла класса Java эта информация сохраняется в каждом методе, что позволяет только внедрять такую ​​информацию метода для определенных методов.

Повесьте вещи и сильно свалите свою JVM

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

Аннотировать тип приемника конструктора, когда нет внешнего класса

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

Поскольку Foo.class.getDeclaredConstructor().getAnnotatedReceiverType() , однако, возвращает объект AnnotatedType представляющий Foo , можно включить аннотации типов для конструктора Foo непосредственно в файл класса, где эти аннотации позже считываются API-интерфейсом отражения.

Использовать неиспользуемые / устаревшие инструкции кода байта

Поскольку другие назвали его, я также включу его. Ранее Java использовала подпрограммы с помощью инструкций JSR и RET . JBC даже знал свой тип обратного адреса для этой цели. Однако использование подпрограмм делало слишком сложным статический анализ кода, поэтому эти инструкции больше не используются. Вместо этого компилятор Java будет дублировать код, который он компилирует. Тем не менее, это в основном создает идентичную логику, и поэтому я не считаю ее достижением чего-то другого. Аналогичным образом, вы можете, например, добавить инструкцию байтового кода NOOP которая не используется компилятором Java, но это не позволило бы вам добиться чего-то нового. Как указано в контексте, эти упомянутые «функциональные инструкции» теперь удаляются из набора юридических кодов операций, что делает их еще менее характерными.

Может быть, раздел 7A в этом документе представляет интерес, хотя речь идет об ошибках байт-кода, а не о байт- кодах .

  • GOTO можно использовать с ярлыками для создания собственных структур управления ( for исключением того, for while т. Д.),
  • Вы можете переопределить this локальную переменную внутри метода
  • Объединив оба из них, вы можете создать оптимизированный байт-код для создания хвоста (я делаю это в JCompilo )

В качестве связанной точки вы можете получить имя параметра для методов, если скомпилировано с помощью debug ( Paranamer делает это, читая байт-код

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

Однако некоторые функции, которые не производятся современными компиляторами Java, однако:

Это флаг, который может быть установлен в классе, и указывает, как обрабатывается конкретный угловой случай invokespecial байт-кода для этого класса. Он задается всеми современными компиляторами Java (где «modern» is> = Java 1.1, если я правильно помню), и только древние компиляторы Java создавали файлы классов, где это не было установлено. Этот флаг существует только для соображений обратной совместимости. Обратите внимание, что, начиная с Java 7u51, ACC_SUPER полностью игнорируется из-за соображений безопасности.

Байт-коды jsr / ret .

Эти байт-коды использовались для реализации подпрограмм (в основном для реализации блоков finally ). Они больше не производятся с Java 6 . Причиной их устаревания является то, что они усложняют статическую проверку для большого выигрыша (т. Е. Используемый код почти всегда может быть реализован с помощью обычных переходов с очень небольшими накладными расходами).

Имеет два метода в классе, которые отличаются только типом возврата.

Спецификация языка Java не позволяет использовать два метода в одном классе, если они отличаются только их возвращаемым типом (то же имя, тот же список аргументов, . ). Однако спецификация JVM не имеет такого ограничения, поэтому файл класса может содержать два таких метода, просто невозможно создать такой файл класса, используя обычный компилятор Java. В этом ответе есть хороший пример / объяснение.

В Java, если вы попытаетесь переопределить общедоступный метод с помощью защищенного метода (или любого другого сокращения доступа), вы получите сообщение об ошибке: «попытка назначить более слабые права доступа». Если вы делаете это с помощью байт-кода JVM, верификатор в порядке с ним, и вы можете вызвать эти методы через родительский класс, как если бы они были общедоступными.

В языке Java первый оператор в конструкторе должен быть вызовом конструктора суперкласса. Байт-код не имеет этого ограничения, вместо этого правило заключается в том, что конструктор супер-класса или другой конструктор в том же классе должен быть вызван для объекта перед доступом к членам. Это должно позволить больше свободы, такой как:

  • Создайте экземпляр другого объекта, сохраните его в локальной переменной (или стеке) и передайте его как параметр суперклассу, сохраняя при этом ссылку в этой переменной для другого использования.
  • Вызовите другие конструкторы на основе условия. Это должно быть возможным: как условно вызывать другой конструктор на Java?

Я не тестировал их, поэтому, пожалуйста, поправьте меня, если я ошибаюсь.

Вот некоторые функции, которые можно выполнить в байт-коде Java, но не в исходном коде Java:

Выбрасывание проверенного исключения из метода, не объявляя, что метод выбрасывает его. Проверенные и непроверенные исключения — это вещь, которая проверяется только компилятором Java, а не JVM. Из-за этого, например, Scala может выдавать проверенные исключения из методов, не объявляя их. Хотя с Java-дженериками есть обходной путь, называемый « подлый бросок» .

Имея два метода в классе, которые отличаются только в обратном типе, как уже упоминалось в ответе Йоахима : Спецификация языка Java не позволяет использовать два метода в одном классе, когда они отличаются только их возвращаемым типом (то же имя, тот же список аргументов, . ). Однако спецификация JVM не имеет такого ограничения, поэтому файл класса может содержать два таких метода, просто невозможно создать такой файл класса, используя обычный компилятор Java. В этом ответе есть хороший пример / объяснение.

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

Добавить комментарий