Детерминировано ли создание файлов классов Java?

94

При использовании одного и того же JDK (т.е. одного и того же javacисполняемого файла) всегда ли сгенерированные файлы классов идентичны? Может быть разница в зависимости от операционной системы или оборудования ? Могут ли быть какие-то различия, кроме версии JDK? Есть ли какие-либо параметры компилятора, чтобы избежать различий? Возможно ли различие только в теории или Oracle javacдействительно создает разные файлы классов для одних и тех же входных данных и параметров компилятора?

Обновление 1 Меня интересует генерация , то есть вывод компилятора, а не то, можно ли запускать файл класса на различных платформах.

Обновление 2 Под «Same JDK» я также подразумеваю тот же javacисполняемый файл.

Обновление 3 Различие между теоретической и практической разницей в компиляторах Oracle.

[РЕДАКТИРОВАТЬ, добавление перефразированного вопроса]
«Каковы обстоятельства, при которых один и тот же исполняемый файл javac при запуске на другой платформе создает другой байт-код?»

mstrap
источник
5
@Gamb CORA не означает, что байтовый код будет точно таким же, если скомпилирован на разных платформах; все это означает, что сгенерированный байт-код будет делать то же самое.
dasblinkenlight
10
Почему тебя это беспокоит? Это пахнет проблемой XY .
Иоахим Зауэр
4
@JoachimSauer Подумайте, управляете ли вы версией своих двоичных файлов - вы можете обнаруживать изменения только в том случае, если исходный код был изменен, но вы бы знали, что это не разумная идея, если JDK может произвольно изменять выходные двоичные файлы.
РБ.
7
@RB .: компилятору разрешено создавать любой соответствующий байтовый код, представляющий скомпилированный код. Фактически, некоторые обновления компилятора исправляют ошибки, приводящие к несколько иному коду (обычно с одинаковым поведением во время выполнения). Другими словами: если вы хотите обнаружить изменения источника, проверьте наличие изменений источника.
Joachim Sauer
3
@dasblinkenlight: вы предполагаете, что ответ, который они утверждают, действительно правильный и актуальный (сомнительно, учитывая, что вопрос с 2003 года).
Joachim Sauer

Ответы:

68

Скажем так:

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

Я мог бы сделать это, настроив все виды конструкции байт-кода или просто добавив лишние атрибуты в свой метод (что разрешено).

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

Однако несколько раз, которые я проверял, компиляция одного и того же исходного файла с тем же компилятором с теми же переключателями (и теми же библиотеками!) Приводила к одним и тем же .classфайлам.

Обновление: недавно я наткнулся на этот интересный пост в блоге о реализации switchon Stringв Java 7 . В этом сообщении блога есть некоторые важные части, которые я процитирую здесь (выделено мной):

Чтобы сделать вывод компилятора предсказуемым и повторяемым, в этих структурах данных используются карты и наборы LinkedHashMaps и LinkedHashSets, а не просто HashMapsи HashSets. С точки зрения функциональной корректности кода , сгенерированного в течение заданного компиляции, используя HashMapи HashSetбыло бы хорошо ; порядок итерации не имеет значения. Однако мы считаем полезным, чтобы javacвыходные данные не менялись в зависимости от деталей реализации системных классов .

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

Иоахим Зауэр
источник
@GaborSch чего не хватает? «Каковы обстоятельства, при которых один и тот же исполняемый файл javac при запуске на другой платформе создает другой байт-код?» в основном в зависимости от прихоти группы, которая создала компилятор
Emory
3
Что ж, для меня этого было бы достаточным основанием не зависеть от него: обновленный JDK мог сломать мою систему сборки / архивирования, если бы я зависел от того факта, что компилятор всегда производит один и тот же код.
Иоахим Зауэр
3
@GaborSch: у вас уже есть прекрасный пример такой ситуации, так что хотелось бы немного подробнее рассмотреть проблему. Нет смысла дублировать вашу работу.
Иоахим Зауэр,
1
@GaborSch Основная проблема заключается в том, что мы хотим реализовать эффективное «онлайн-обновление» нашего приложения, для которого пользователи будут получать только модифицированные JAR-файлы с веб-сайта. Я могу создавать идентичные JAR-файлы с идентичными файлами классов на входе. Но вопрос в том, всегда ли файлы классов идентичны при компиляции из одних и тех же исходных файлов. Вся наша концепция стоит и не соответствует этому факту.
mstrap
2
@mstrap: значит, это проблема XY. Что ж, вы можете изучить дифференциальные обновления jar-файлов (так что даже однобайтовые различия не приведут к повторной загрузке всего jar-файла), и вы все равно должны указывать явные номера версий для своих выпусков, так что, на мой взгляд, весь этот вопрос спорный .
Иоахим Зауэр
38

Компиляторы не обязаны создавать один и тот же байт-код на каждой платформе. 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); 

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

Габорш
источник
1
+1 Между компилятором Eclipse и javac существует множество различий, например, как создаются синтетические конструкторы .
Пол Беллора
2
@GaborSch Меня интересует, идентичен ли байт-код для одного и того же JDK, т.е. одного и того же javac. Я поясню это.
mstrap
2
@mstrap Я понял ваш вопрос, но ответ все тот же: зависит от производителя. Это javacне то же самое, потому что у вас разные двоичные файлы на каждой платформе (например, Win7, Linux, Solaris, Mac). Для поставщика не имеет смысла иметь разные реализации, но любая проблема, связанная с платформой, может повлиять на результат (например, упорядочение полета в каталоге (подумайте о своем libкаталоге), порядок байтов и т. Д.).
gaborsch
1
Обычно большая часть из javacних реализована на Java (и javacпредставляет собой простую встроенную программу запуска), поэтому большинство различий платформ не должны иметь никакого влияния.
Joachim Sauer
2
@mstrap - точка он делает то , что не существует требование для любого поставщика , чтобы сделать свой компилятор производить точно такой же байт - кода на разных платформах, только что полученный байткод производит те же самые результаты. Учитывая, что стандарта / спецификации / требования нет, ответ на ваш вопрос: «Это зависит от конкретного поставщика, компилятора и платформы».
Брайан Роуч,
6

Короткий ответ - НЕТ


Длинный ответ

Они bytecodeне обязательно должны быть одинаковыми для разных платформ. Это JRE (среда выполнения Java), которая знает, как именно выполнять байт-код.

Если вы пройдете через спецификацию 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 являются второстепенным и основным номерами версий этого файла класса. Вместе старший и дополнительный номер версии определяют версию формата файла класса. Если файл класса имеет номер основной версии M и дополнительный номер версии m, мы обозначаем версию его формата файла класса как Mm. Таким образом, версии формата файла класса могут быть упорядочены лексикографически, например, 1.5 <2.0 <2.1. Реализация виртуальной машины Java может поддерживать формат файла класса версии v тогда и только тогда, когда v лежит в некотором непрерывном диапазоне Mi.0 v Mj.m. Только Sun может указать, какой диапазон версий может поддерживать реализация виртуальной машины Java, соответствующая определенному уровню выпуска платформы Java1.

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

1 Реализация виртуальной машины Java версии Sun JDK 1.0.2 поддерживает версии формата файлов классов с 45.0 по 45.3 включительно. Выпуск Sun JDK 1.1.X может поддерживать форматы файлов классов версий в диапазоне от 45.0 до 45.65535 включительно. Реализации версии 1.2 платформы Java 2 могут поддерживать форматы файлов классов версий в диапазоне от 45.0 до 46.0 включительно.

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

mtk
источник
Можете дать более подробную ссылку?
mstrap
Я думаю, что под «платформой» они имеют в виду платформу Java, а не операционную систему. Конечно, при указании javac 1.7 создать файлы классов, совместимые с 1.6, будет разница.
mstrap
@mtk +1, чтобы показать, сколько свойств создается для одного класса во время компиляции.
gaborsch
3

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

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

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

В-четвертых (и что наиболее важно) от желания узнать это пахнет дурным запахом процесса (например, запахом кода, но только в отношении того, как вы действуете с кодом). Если возможно, обновляйте исходный код, а не сборку, и, если вам действительно нужно версировать сборку, версию на уровне всего компонента, а не для отдельных файлов классов. Предпочтительно используйте CI-сервер (например, Jenkins) для управления процессом преобразования исходного кода в исполняемый код.

Donal Fellows
источник
2

Я считаю, что если вы используете один и тот же JDK, сгенерированный байт-код всегда будет одинаковым, независимо от используемого оборудования и ОС. Создание байтового кода выполняется компилятором java, который использует детерминированный алгоритм для «преобразования» исходного кода в байтовый код. Таким образом, результат всегда будет одинаковым. В этих условиях только обновление исходного кода повлияет на вывод.

Viniciusjssouza
источник
3
У вас есть ссылка на это? Как я сказал в комментариях к вопросу, это определенно не относится к C # , поэтому хотелось бы увидеть ссылку, в которой говорится, что это относится к Java. Я особенно думаю, что многопоточный компилятор может назначать разные имена идентификаторов при разных запусках.
РБ.
1
Это ответ на мой вопрос и то, чего я ожидал, однако я согласен с RB в том, что ссылка на это будет важна.
mstrap
Я верю в то же самое. Не думаю, что вы найдете исчерпывающую ссылку. Если это важно для вас, вы можете провести исследование. Соберите несколько лучших и опробуйте их на разных платформах, скомпилировав некоторый открытый исходный код. Сравните байтовые файлы. Опубликуйте результат. Обязательно поместите сюда ссылку.
emory
1

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

Я бы рассмотрел сценарии с использованием разных языков (кодовых страниц), например Windows с поддержкой японского языка. Подумайте о многобайтовых символах; если компилятор не всегда предполагает, что ему необходимо поддерживать все языки, которые он может оптимизировать для 8-битного ASCII.

В Спецификации языка Java есть раздел о двоичной совместимости .

В рамках бинарной совместимости от выпуска к выпуску в SOM (Forman, Conner, Danforth и Raper, Proceedings of OOPSLA '95) двоичные файлы языка программирования Java являются двоично совместимыми при всех соответствующих преобразованиях, которые указывают авторы (с некоторыми оговорками в отношении относительно добавления переменных экземпляра). Используя их схему, вот список некоторых важных двоичных совместимых изменений, которые поддерживает язык программирования Java:

• Повторная реализация существующих методов, конструкторов и инициализаторов для повышения производительности.

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

• Добавление новых полей, методов или конструкторов в существующий класс или интерфейс.

• Удаление частных полей, методов или конструкторов класса.

• При обновлении всего пакета удаление полей доступа по умолчанию (только для пакета), методов или конструкторов классов и интерфейсов в пакете.

• Изменение порядка полей, методов или конструкторов в существующем объявлении типа.

• Перемещение метода вверх по иерархии классов.

• Изменение порядка в списке прямых суперинтерфейсов класса или интерфейса.

• Вставка новых типов классов или интерфейсов в иерархию типов.

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

Келли С. Френч
источник
В этой статье обсуждается, что может случиться, если мы изменим версию Java. Вопрос OP заключался в том, что может произойти, если мы изменим платформу в той же версии Java. В противном случае это хороший улов.
gaborsch
1
Это настолько близко, насколько я смог найти. Между спецификацией языка и спецификацией JVM есть странная дыра. До сих пор мне пришлось бы ответить на OP словами: «Нет никакой гарантии, что один и тот же компилятор java создаст тот же байт-код при запуске на другой платформе».
Келли С. Френч,
1

Java allows you write/compile code on one platform and run on different platform. AFAIK ; это будет возможно только тогда, когда файл класса, созданный на другой платформе, будет одинаковым или технически одинаковым, т.е. идентичным.

редактировать

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

Таким образом, согласно спецификации .class-файлу класса на разных платформах не требуется побайтовое сопоставление.

rai.skumar
источник
Вопрос OP заключался в том, были ли файлы классов одинаковыми или «технически одинаковыми».
bdesham
Мне интересно, идентичны ли они .
mstrap
и ответ "да". Я имею в виду, что они могут быть разными, если сравнивать побайтово, поэтому я использовал слово технически одинаково.
rai.skumar
@bdesham он хотел знать, идентичны ли они. не уверен, что вы понимаете под «технически то же самое» ... это причина отрицательного голоса?
rai.skumar
@ rai.skumar В основном ваш ответ гласит: «Два компилятора всегда производят одинаковый результат». Конечно, это правда; в этом вся мотивация платформы Java. OP хотел знать, был ли испущенный код побайтно идентичным , что вы не указали в своем ответе.
bdesham
1

На вопрос:

«Каковы обстоятельства, при которых один и тот же исполняемый файл javac при запуске на другой платформе создает другой байт-код?»

В пример кросс-компиляции показывает , как мы можем использовать опцию JAVAC: -target версия

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

Филип Хосе Парампетту
источник
0

Скорее всего, ответ - «да», но чтобы получить точный ответ, нужно поискать какие-то ключи или генерацию guid во время компиляции.

Я не могу вспомнить ситуацию, когда это произошло. Например, чтобы иметь идентификатор для сериализации, он жестко запрограммирован, то есть генерируется программистом или IDE.

PS Также может иметь значение JNI.

PPS Я обнаружил, что javacсамо написано на java. Это означает, что он идентичен на разных платформах. Следовательно, он не будет генерировать другой код без причины. Таким образом, он может делать это только с собственными вызовами.

Сьюзан Чиок
источник
Обратите внимание, что Java не защищает вас от всех различий в платформах. Порядок файлов, возвращаемых при выводе списка содержимого каталога, не определен, и это, вероятно, может иметь некоторое влияние на компилятор.
Joachim Sauer
0

Есть два вопроса.

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?

Это непостижимо.

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

Пропустить Эддисон
источник
0

Я бы сказал иначе.

Во-первых, я думаю, что вопрос не в детерминированности:

Конечно, он детерминирован: в информатике трудно добиться случайности, и нет причин, по которым компилятор по какой-либо причине вводит ее здесь.

Во-вторых, если вы переформулируете его следующим образом: «Насколько похожи файлы байт-кода для одного и того же файла исходного кода?», То нет , вы не можете полагаться на тот факт, что они будут похожими .

Хороший способ убедиться в этом - оставить .class (или .pyc в моем случае) на этапе git. Вы поймете, что на разных компьютерах в вашей команде git замечает изменения между файлами .pyc, когда никакие изменения не были внесены в файл .py (и .pyc все равно перекомпилирован).

По крайней мере, я это заметил. Так что поместите * .pyc и * .class в свой .gitignore!

Огюстен Ридингер
источник