Чи є створення файлів класу Java детермінованим?


94

Чи використовуються однакові файли JDK (тобто той самий javacвиконуваний файл), завжди збігаються файли класів? Чи може бути різниця залежно від операційної системи чи обладнання ? За винятком версії JDK, чи можуть існувати якісь інші фактори, що призводять до відмінностей? Чи є варіанти компілятора, щоб уникнути відмінностей? Чи є різниця лише теоретично чи Oracle javacнасправді створює різні файли класів для однакових опцій вводу та компілятора?

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

Оновлення 2 Під «Тим самим JDK» я маю на увазі той самий javacвиконуваний файл.

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

[EDIT, додавши перефразоване запитання]
"Які обставини, коли той самий виконуваний файл javac, коли запускається на іншій платформі, буде створювати інший байт-код?"


5
@Gamb CORA не означає, що байт-код буде абсолютно однаковим, якщо його компілювати на різних платформах; все це означає, що згенерований байт-код буде робити те саме саме.
dasblinkenlight

10
Чому ви дбаєте? Це пахне проблемою XY .
Йоахім Зауер

4
@JoachimSauer Подумайте, чи керуєте ви версією своїх двійкових файлів - можливо, ви захочете виявити зміни, лише якщо змінився вихідний код, але ви б знали, що це не було розумною ідеєю, якщо JDK може довільно змінювати вихідні двійкові файли.
РБ.

7
@RB .: компілятору дозволено створювати будь-який відповідний байт-код, який представляє скомпільований код. Насправді, деякі оновлення компілятора виправляють помилки, які видають дещо інший код (зазвичай з однаковою поведінкою під час виконання). Іншими словами: якщо ви хочете виявити зміни у джерелі, перевірте наявність змін у джерелі.
Йоахім Зауер

3
@dasblinkenlight: ви припускаєте, що відповідь, яку вони стверджують, насправді є правильною та актуальною (сумнівна, враховуючи те, що питання з 2003 року).
Йоахім Зауер

Відповіді:


68

Скажімо так:

Я легко можу створити повністю відповідний компілятор Java, який ніколи не створює один і той же .classфайл двічі, враховуючи той самий .javaфайл.

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

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

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

Оновлення: Нещодавно я натрапив на цю цікаву публікацію в блозі про реалізацію switchon Stringу Java 7 . У цьому дописі в блозі є кілька відповідних частин, які я цитую тут (наголос на моєму):

Для того, щоб зробити висновки компілятора передбачуваними та повторюваними, карти та набори, що використовуються у цих структурах даних, є LinkedHashMaps і LinkedHashSets, а не просто HashMapsі HashSets. З точки зору функціональної коректності коду, сформованого під час даної компіляції, використання HashMapі HashSetбуло б чудово ; порядок ітерацій не має значення. Однак ми вважаємо вигідним, щоб javacвихідні дані не змінювались залежно від деталей реалізації системних класів .

Це досить наочно ілюструє проблему: компілятор не зобов'язаний діяти детерміновано, якщо він відповідає специфікації. Однак розробники компілятора розуміють, що, як правило, непогано спробувати (за умови, що це, мабуть, не надто дорого).


@GaborSch чого йому не вистачає? "Які обставини, коли один і той же виконуваний файл javac під час запуску на іншій платформі створюватиме інший байт-код?" в основному залежно від примхи групи, яка створила упорядника
Еморі

3
Ну, для мене це було б достатньою причиною, щоб не залежати від цього: оновлений JDK міг зламати мою систему побудови / архівування, якби я залежав від того, що компілятор завжди виробляє один і той же код.
Йоахім Зауер

3
@GaborSch: у вас вже є чудовий приклад такої ситуації, тому деякий додатковий погляд на проблему був у порядку. Дублювати вашу роботу немає сенсу.
Йоахім Зауер,

1
@GaborSch Корінна проблема полягає в тому, що ми хочемо здійснити ефективне "оновлення в Інтернеті" нашого додатку, для якого користувачі отримуватимуть лише змінені JAR-файли з веб-сайту. Я можу створити однакові JAR-файли, що мають ідентичні файли класів як вхідні дані. Але питання полягає в тому, чи завжди ідентичні файли класів при компіляції з тих самих вихідних файлів. Вся наша концепція стоїть і зазнає невдачі з цим фактом.
mstrap

2
@mstrap: отже, це все-таки проблема XY. Ну, ви можете вивчити диференціальні оновлення банок (так що навіть однобайтові різниці не спричинять повторного завантаження цілого банку), і ви все одно повинні вказувати чіткі номери версій для своїх випусків, так що, на мій погляд, вся справа в цьому спірна .
Йоахім Зауер

38

Компілятори не зобов’язані створювати однаковий байт-код на кожній платформі. Вам слід проконсультуватися з javacутилітою різних постачальників, щоб отримати конкретну відповідь.


Для цього я покажу практичний приклад із упорядкуванням файлів.

Скажімо, у нас є 2 файли jar: my1.jarі My2.jar. Вони поміщаються в libкаталог, поруч. Компілятор читає їх в алфавітному порядку (оскільки це так lib), але порядок є my1.jar, My2.jarколи файлова система не враховує регістр та My2.jar, my1.jarякщо вона чутлива до регістру.

The my1.jarмає клас A.classіз методом

public class A {
     public static void a(String s) {}
}

The My2.jarмає однаковий A.class, але з іншим підписом методу (приймає Object):

public class A {
     public static void a(Object o) {}
}

Зрозуміло, що якщо у вас є дзвінок

String s = "x"; 
A.a(s); 

він скомпілює виклик методу з різним підписом у різних випадках. Отже, залежно від чутливості регістру вашої файлової системи, в результаті ви отримаєте інший клас.


1
+1 Між компілятором Eclipse та javac існує безліч відмінностей, наприклад, як генеруються синтетичні конструктори .
Paul Bellora

2
@GaborSch Мене цікавить, чи ідентичний код байта для того самого JDK, тобто того самого javac. Я зроблю це зрозумілішим.
mstrap

2
@mstrap Я зрозумів ваше запитання, але відповідь все така ж: залежить від постачальника. Це javacне однаково, оскільки у вас є різні двійкові файли на кожній платформі (наприклад, Win7, Linux, Solaris, Mac). Для постачальника не має сенсу мати різні реалізації, але будь-яка специфічна проблема платформи може вплинути на результат (наприклад, замовлення файлів у каталозі (подумайте про свій libкаталог), ендіанс тощо).
gaborsch

1
Зазвичай більшість із javacних реалізовано на Java (і javacце просто простий власний запуск), тому більшість відмінностей платформи не повинні мати ніякого впливу.
Йоахім Зауер

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, що відповідає певному рівню випуску платформи Java. 1

Більше читання через виноски

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

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


Чи можете ви дати більш детальне посилання, будь ласка?
mstrap

Я думаю, що під "платформою" вони мають на увазі платформу Java, а не операційну систему. Звичайно, коли вказівка ​​javac 1.7 створювати 1.6-сумісні файли класу, буде різниця.
mstrap

@mtk +1, щоб показати, скільки властивостей створюється для одного класу під час компіляції.
gaborsch

3

По-перше, такої гарантії в специфікації немає абсолютно. Відповідний компілятор може позначити час компіляції у згенерованому файлі класу як додатковий (спеціальний) атрибут, і файл класу все одно буде правильним. Однак він створить інший файл на рівні байта для кожної окремої збірки, і це тривіально.

По-друге, навіть без таких неприємних хитрощів немає жодних підстав очікувати, що компілятор робитиме одне і те ж двічі поспіль, якщо обидва варіанти його конфігурації та введення не будуть однаковими. Специфікація робить опис вихідного файлу в якості одного зі стандартних атрибутів, і додавання порожніх рядків у вихідному файл також може змінити таблицю номера рядка.

По-третє, я ніколи не стикався з будь-якою різницею у побудові через хост-платформу (крім тієї, яка була обумовлена ​​різницею в тому, що було на шляху до класу). Код, який змінюватиметься залежно від платформи (тобто бібліотеки власного коду), не є частиною файлу класу, і фактичне створення власного коду з байт-коду відбувається після завантаження класу.

По-четверте (і найголовніше), пахне поганим запахом процесу (як запах коду, але тим, як ви дієте на коді), щоб хотіти це знати. Якщо можливо, встановіть версію джерела, а не збірки, а якщо вам потрібно встановити версію збірки, версію на рівні всього компонента, а не на окремих файлах класів. Для переваги використовуйте сервер CI (наприклад, Дженкінс), щоб керувати процесом перетворення вихідного коду в код, який можна запустити.


2

Я вважаю, що якщо ви використовуєте один і той же JDK, згенерований байт-код завжди буде однаковим, не пов'язаний із використовуваним програмним забезпеченням та ОС. Виробництво байтового коду виконується компілятором Java, який використовує детермінований алгоритм для "перетворення" вихідного коду в байтовий код. Отже, результат завжди буде однаковим. У цих умовах лише оновлення вихідного коду вплине на результат.


3
У вас є посилання на це, хоча? Як я вже говорив у коментарях до запитання, це точно не так для C # , тому я хотів би побачити посилання, в якому зазначається, що це стосується Java. Я особливо думаю, що багатопотоковий компілятор може призначати різні імена ідентифікаторів у різних прогонах.
РБ.

1
Це відповідь на моє запитання і те, що я очікував би, проте я погоджуюсь з РБ, що посилання на це було б важливим.
mstrap

Я вірю в те саме. Не думаю, що ви знайдете остаточне посилання. Якщо це важливо для вас, тоді ви можете провести дослідження. Зберіть купу провідних і спробуйте їх на різних платформах, складаючи якийсь відкритий вихідний код. Порівняйте файли байтів. Опублікуйте результат. Обов’язково розмістіть тут посилання.
emory

1

Загалом, я повинен сказати, що немає жодних гарантій того, що одне і те ж джерело створить той самий байт-код, коли його компілює той самий компілятор, але на іншій платформі.

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

У Специфікації мови Java є розділ про бінарну сумісність .

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

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

• Зміна методів або конструкторів для повернення значень на входах, для яких вони раніше або викидали винятки, які зазвичай не повинні відбуватися, або не вдалися, потрапивши в нескінченний цикл або спричинивши глухий кут.

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

• Видалення приватних полів, методів або конструкторів класу.

• Коли оновлюється весь пакет, видаляються поля доступу, методи або конструктори класів та інтерфейсів за замовчуванням (лише для пакету).

• Переупорядкування полів, методів або конструкторів у існуючій декларації типу.

• Переміщення методу вгору в ієрархії класів.

• Впорядкування списку прямих суперінтерфейсів класу або інтерфейсу.

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

Цей розділ визначає мінімальні стандарти двійкової сумісності, гарантовані всіма реалізаціями. Мова програмування Java гарантує сумісність при змішуванні двійкових файлів класів та інтерфейсів, які, як відомо, не надходять із сумісних джерел, але джерела яких були змінені описаними тут сумісними способами. Зверніть увагу, що ми обговорюємо сумісність між випусками програми. Обговорення сумісності між випусками платформи Java SE виходить за рамки цього розділу.


У цій статті йдеться про те, що може статися, коли ми змінимо версію Java. Питання OP полягало в тому, що може статися, якщо ми змінимо платформу в одній версії Java. В іншому випадку це хороший улов.
gaborsch

1
Це так близько, як я міг знайти. Між специфікацією мови та специфікацією JVM існує дивна діра. Поки що мені доведеться відповісти на операційну програму, сказавши: "немає гарантії, що один і той же компілятор Java створить один і той же байт-код при запуску на іншій платформі".
Келлі С. Френч,

1

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

Редагувати

Що я маю на увазі під технічно однаковим коментарем, це те, що. Вони не повинні бути абсолютно однаковими, якщо порівнювати байт за байтом.

Отже, згідно специфікації .class-файлу класу на різних платформах не потрібно збігати байт-за-байтом.


Питання OP полягало в тому, однакові файли класів чи "технічно однакові".
bdesham

Мене цікавить, чи однакові вони .
mstrap

і відповідь - так. я маю на увазі, що вони можуть бути не однаковими, якщо порівнювати байт за байтом, саме тому я використав слово технічно однаково.
rai.skumar

@bdesham він хотів знати, чи вони однакові. не впевнений, що ви зрозуміли під "технічно однаковим" ... чи причина в цьому голосування?
rai.skumar

@ rai.skumar Ваша відповідь в основному говорить: "Два компілятори завжди видаватимуть результат, який поводиться однаково". Звичайно, це правда; це вся мотивація платформи Java. OP хотів би знати, чи випущений код байт для байта ідентичний , на що ви не зверталися у своїй відповіді.
bdesham

1

Для питання:

"Які обставини, коли той самий виконуваний файл javac під час запуску на іншій платформі створюватиме інший байт-код?"

Приклад перехресної компіляції показує, як ми можемо використовувати опцію Javac: -target версія

Цей прапор генерує файли класів, сумісні з версією Java, яку ми вказуємо під час виклику цієї команди. Отже, файли класів будуть відрізнятися залежно від атрибутів, які ми надаємо під час порівняння, використовуючи цю опцію.


0

Швидше за все, відповідь "так", але щоб отримати точну відповідь, потрібно шукати якісь ключі або генерувати під час компіляції.

Я не можу згадати ситуацію, коли це відбувається. Наприклад, щоб мати ID для цілей серіалізації, він жорстко закодований, тобто генерується програмістом або IDE.

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

PPS Я виявив, що javacсаме це написано на Java. Це означає, що він ідентичний на різних платформах. Отже, він не буде генерувати різний код без причини. Отже, це можна зробити лише за допомогою власних дзвінків.


Зауважте, що Java не захищає вас від усіх відмінностей платформи. Порядок файлів, що повертаються при переліку вмісту каталогів, не визначений, і це, можливо, може мати певний вплив на компілятор.
Йоахім Зауер

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!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.