Является ли создание файлов классов Java детерминированным?
При использовании одного и того же JDK (т.е. того же исполняемого файла javac
) генерируются ли файлы сгенерированных файлов одинаково? Может ли быть разница в зависимости от операционной системы или оборудования? Могут ли быть какие-либо другие факторы, приводящие к различиям, кроме версии JDK? Существуют ли какие-либо параметры компилятора, чтобы избежать различий? Является ли разница только теоретически или Oracle javac
действительно создает разные файлы классов для тех же параметров ввода и компилятора?
Обновление 1. Меня интересует генерация, т.е. выход компилятора, а не то, может ли файл класса запускаться на разных платформах.
Обновление 2. "То же JDK", я также имею в виду тот же исполняемый файл javac
.
Обновление 3 Различие между теоретической разницей и практической разницей в компиляторах Oracle.
[EDIT, добавив перефразируемый вопрос]
"Каковы обстоятельства, когда один и тот же исполняемый файл javac, когда он запускается на другой платформе, будет создавать разные байт-коды?"
Ответы
Ответ 1
Скажем так:
Я могу легко создать полностью соответствующий Java-компилятор, который никогда не создает один и тот же файл .class
дважды, учитывая тот же файл .java
.
Я мог бы сделать это, изменив все виды построения байткода или просто добавив лишние атрибуты к моему методу (что разрешено).
Учитывая, что спецификация не требует, чтобы компилятор создавал файлы с одинаковым классом byte-by-byte, я бы избегал такого результата.
Однако несколько раз, что я проверил, компиляция одного и того же исходного файла с одним и тем же компилятором с теми же ключами (и теми же библиотеками!) привела к тем же файлам .class
.
Обновление: я недавно наткнулся на эту интересную запись в блоге о реализации switch
на String
в Java 7. В этом сообщении в блоге есть некоторые важные части, которые я приведу здесь (основное внимание):
Чтобы сделать вывод компилятора предсказуемым и повторяемым, карты и наборы, используемые в этих структурах данных, LinkedHashMap
и LinkedHashSet
, а не только HashMaps
и HashSets
. В терминах функциональной корректности генерируемого кода во время данной компиляции с использованием HashMap
и HashSet
будет отлично; порядок итераций не имеет значения. Однако мы считаем полезным, чтобы вывод javac
не менялся в зависимости от деталей реализации системных классов.
Это довольно ясно иллюстрирует проблему: компилятор не обязан действовать детерминированным образом, если он соответствует спецификации. Однако разработчики компилятора понимают, что, как правило, рекомендуется попробовать (при условии, что это не слишком дорого, возможно).
Ответ 2
Компиляторы не обязаны создавать один и тот же байт-код на каждой платформе. Чтобы получить конкретный ответ, обратитесь к утилите javac
разных поставщиков.
Я покажу вам практический пример этого с упорядочением файлов.
Скажем, что у нас есть 2 файла jar: my1.jar
и My2.jar
. Они помещаются в каталог lib
, бок о бок. Компилятор читает их в алфавитном порядке (поскольку это lib
), но порядок my1.jar
, My2.jar
, когда файловая система нечувствительна к регистру, и My2.jar
, my1.jar
, если она чувствительна к регистру.
my1.jar
имеет класс A.class
с методом
public class A {
public static void a(String s) {}
}
My2.jar
имеет тот же A.class
, но с другой сигнатурой метода (принимает Object
):
public class A {
public static void a(Object o) {}
}
Ясно, что если у вас есть вызов
String s = "x";
A.a(s);
он будет компилировать вызов метода с разной подписью в разных случаях. Таким образом, в зависимости от чувствительности вашего файлового систем вы получите в результате другой класс.
Ответ 3
Короткий ответ - НЕТ
Длинный ответ
Они bytecode
не обязательно должны быть одинаковыми для разных платформ. Это JRE (Java Runtime Environment), которые знают, как именно выполнить байт-код.
Если вы пройдете спецификацию Java VM, вы узнаете, что это не должно быть правдой, что байт-код одинаковый для разных платформы.
Просматривая формат файла класса, он отображает структуру файла класса как
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
Проверка малой и основной версии
minor_version, major_version
Значения minor_version и Элементы major_version - это младшие и основные номера версий этого файл класса. В целом, основной и младший номера версии определяют версию формата файла класса. Если файл класса имеет основную версию номер M и младший номер версии m, обозначим версию его формат файла класса в формате M.m. Таким образом, версии формата файла классов могут быть упорядоченный лексикографически, например, 1,5 < 2,0 < 2.1. Java реализация виртуальной машины может поддерживать формат файла класса версия v тогда и только тогда, когда v лежит в некотором смежном диапазоне Mi.0 v Mj.m. Только Sun может указать, какой диапазон версий виртуальный Java в соответствии с определенным уровнем выпуска Платформа Java может поддерживать .1
Чтение более через сноски
1 Реализация виртуальной машины Java версии Sun JDK версии 1.0.2 поддерживает версии файлов классов версии от 45,0 до 45,3 включительно. Солнца JDK выпускает 1.1.X может поддерживать форматы файлов классов версий в диапазон от 45,0 до 45,65535 включительно. Реализации версии 1.2 платформы Java 2 может поддерживать форматы файлов классов версий в диапазон от 45,0 до 46,0 включительно.
Итак, исследование всего этого показывает, что файлы классов, сгенерированные на разных платформах, не обязательно должны быть идентичными.
Ответ 4
Во-первых, в спецификации нет такой гарантии. Соответствующий компилятор может штамповать время компиляции в сгенерированный файл класса в качестве дополнительного (настраиваемого) атрибута, и файл класса будет по-прежнему правильным. Тем не менее, он создавал на каждом отдельном сборке файл на уровне байтов и тривиально.
Во-вторых, даже без таких неприятных трюков, нет никаких оснований ожидать, что компилятор сделает то же самое дважды подряд, если только его конфигурация и ее входные данные не совпадают в двух случаях. Спецификация описывает исходное имя файла как один из стандартных атрибутов, а добавление пустых строк в исходный файл может изменить таблицу номеров строк.
В-третьих, я никогда не сталкивался с какой-либо разницей в построении из-за платформы хоста (кроме той, которая была связана с различиями в том, что было в пути к классам). Код, который будет варьироваться в зависимости от платформы (то есть, библиотек собственных кодов), не является частью файла класса, а фактическое генерирование собственного кода из байт-кода происходит после загрузки класса.
В-четвертых (и, самое главное), он пахнет неприятным запахом процесса (например, запахом кода, но для того, как вы действуете на коде), чтобы хотеть это знать. Верните источник, если это возможно, а не сборку, и если вам нужно выполнить версию сборки, версию на уровне всего компонента, а не отдельные файлы классов. Для предпочтения используйте CI-сервер (например, Jenkins) для управления процессом превращения источника в исполняемый код.
Ответ 5
Я считаю, что если вы используете один и тот же JDK, сгенерированный байт-код всегда будет одинаковым, без связи с используемым harware и OS. Генерация байтового кода выполняется компилятором java, который использует детерминированный алгоритм для "преобразования" исходного кода в байтовый код. Таким образом, вывод всегда будет таким же. В этих условиях на выход будет влиять только обновление исходного кода.
Ответ 6
Java allows you write/compile code on one platform and run on different platform.
AFAIK; это будет возможно только тогда, когда файл класса, сгенерированный на другой платформе, будет таким же или технически одним и тем же, то есть идентичным.
Edit
То, что я подразумеваю под технически таким же комментарием, это. Они не должны быть точно такими же, если вы сравниваете байт по байт.
Так что в соответствии со спецификацией .class файл класса на разных платформах не нужно сопоставлять байты по-байтам.
Ответ 7
В целом я должен сказать, что нет гарантии, что один и тот же источник будет генерировать один и тот же байт-код при компиляции одним и тем же компилятором, но на другой платформе.
Я бы рассмотрел сценарии, связанные с разными языками (кодовыми страницами), например Windows с поддержкой японского языка. Думайте многобайтовые символы; если компилятор всегда предполагает, что он должен поддерживать все языки, которые он может оптимизировать для 8-разрядного ASCII.
Существует раздел о бинарной совместимости в Спецификация языка Java.
В рамках бинарной совместимости Release-to-Release в SOM (Форман, Коннер, Данфорт и Рапер, Труды OOPSLA '95), Java двоичные файлы языка программирования являются бинарными, совместимыми во всех соответствующих которые авторы идентифицируют (с некоторыми оговорками с относительно добавления переменных экземпляра). Используя их схему, вот список некоторых важных бинарных совместимых изменений, которые Язык программирования Java поддерживает:
• Повторное использование существующих методов, конструкторов и инициализаторов для повысить производительность.
• Изменение методов или конструкторов для возврата значений на входы, для которых они ранее либо бросали исключения, которые обычно не должны возникать или не удалось, перейдя в бесконечный цикл или создав тупик.
• Добавление новых полей, методов или конструкторов в существующий класс или интерфейс.
• Удаление частных полей, методов или конструкторов класса.
• Когда весь пакет обновляется, удаление по умолчанию (только для пакетов) поля доступа, методы или конструкторы классов и интерфейсов в пакет.
• Переупорядочение полей, методов или конструкторов в существующем типе декларация.
• Перемещение метода вверх в иерархии классов.
• Переупорядочение списка прямых суперинтерфейсов класса или интерфейс.
• Вставка новых классов или типов интерфейсов в иерархию типов.
В этой главе указаны минимальные стандарты бинарной совместимости гарантированный всеми реализациями. Язык программирования Java гарантирует совместимость, когда двоичные классы классов и интерфейсов которые не известны из совместимых источников, но чьи источники были изменены в соответствии с описанными здесь способами. Заметка что мы обсуждаем совместимость между выпусками выражение. Обсуждение совместимости выпусков Java SE платформа выходит за рамки этой главы.
Ответ 8
За вопрос:
"Каковы обстоятельства, когда один и тот же исполняемый файл javac при запуске на другой платформе создает другой байт-код?"
Пример кросс-компиляции показывает, как мы можем использовать опцию Javac: -target version
Этот флаг генерирует файлы классов, которые совместимы с указанной нами версией Java при вызове этой команды. Следовательно, файлы классов будут отличаться в зависимости от атрибутов, которые мы поставляем во время компиляции, используя эту опцию.
Ответ 9
Скорее всего, ответ "да", но для получения точного ответа необходимо выполнить поиск некоторых ключей или генерации ключей во время компиляции.
Я не могу вспомнить ситуацию, когда это происходит. Например, чтобы иметь идентификатор для целей сериализации, он является жестко запрограммированным, то есть сгенерированным программистом или IDE.
P.S. Также JNI может иметь значение.
P.P.S. Я обнаружил, что javac
сам записывается в java. Это означает, что он идентичен на разных платформах. Следовательно, он не будет генерировать другой код без причины. Таким образом, он может делать это только с помощью собственных вызовов.
Ответ 10
Я бы сказал по-другому.
Во-первых, я думаю, вопрос заключается не в том, чтобы быть детерминированным:
Конечно, он детерминирован: случайности в компьютерной науке трудно достичь, и нет причин, по которым компилятор вводит его здесь по любой причине.
Во-вторых, если вы переформулируете его как "как похожи файлы байтов для одного и того же файла исходного кода?", то Нет, вы не можете полагаться на то, что они будут похожи.
Хороший способ убедиться в этом, оставив класс .class(или .pyc в моем случае) на вашем этапе git. Вы поймете, что среди разных компьютеров в вашей команде git замечает изменения между .pyc файлами, когда в файл .py не были внесены изменения (и .pyc перекомпилировано).
По крайней мере, то, что я наблюдал. Поэтому поставьте *.pyc и *.class в свой .gitignore!
Ответ 11
Есть два вопроса.
Can there be a difference depending on the operating system or hardware?
Это теоретический вопрос, и ответ явно, да, там может быть. Как отмечали другие, спецификация не требует от компилятора создания файлов байтов с байтами для байт.
Даже если каждый созданный в настоящий момент компилятор генерирует одинаковый код байта при любых обстоятельствах (другое оборудование и т.д.), завтра ответ может быть другим. Если вы никогда не планируете обновлять javac или вашу операционную систему, вы можете проверить это поведение версии в ваших конкретных обстоятельствах, но результаты могут отличаться, если вы переходите от, например, к Java 7 Update 11 в Java 7 Update 15.
What are the circumstances where the same javac executable, when run on a different platform, will produce different bytecode?
Это непознаваемо.
Я не знаю, является ли управление конфигурацией вашей причиной для запроса вопроса, но это понятная причина для ухода. Сравнение байтовых кодов - это законный ИТ-контроль, но только для того, чтобы определить, изменились ли файлы классов, а не топ, определить, были ли файлы исходного кода.