Является ли создание файлов классов 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?

Это непознаваемо.

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