У чому полягає перетворення вихідного коду в байт-код Java?


37

Якщо потрібні різні JVM для різних архітектур, я не можу зрозуміти, в чому полягає логіка введення цієї концепції. В інших мовах нам потрібні різні компілятори для різних машин, але в Java нам потрібні різні JVM, тож в чому полягає логіка введення концепції JVM або цього додаткового кроку ??



12
@gnat: Насправді це не дублікат. Це "вихідний та байтний код", тобто лише перше перетворення. Мовно, це Javascript проти Java; ваше посилання буде C ++ порівняно з Java.
MSalters

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

1
Ви , здається , не розуміють , що віртуальна машина знаходиться . Це машина. Він може бути реалізований апаратно з компіляторами нативного коду (і у випадку JVM це було). Тут важлива «віртуальна» частина: ви по суті емулюєте цю архітектуру поверх іншої. Скажімо, я написав емулятор 8088 для роботи на x86. Ви не збираєтеся портувати старий код 8088 на x86, ви просто запускаєте його на емульованій платформі. JVM - це машина, на яку ви орієнтуєтеся, як і будь-яка інша, різниця полягає в тому, що вона працює над іншими платформами.
Джаред Сміт

7
@TheGreatDuck Процес тлумачення? Більшість JVM на сьогоднішній день здійснюють своєчасну компіляцію до машинного коду. Не кажучи вже про те, що "інтерпретація" є досить широким поняттям у наш час. Сам процесор просто "інтерпретує" код x86 у свій власний внутрішній мікрокод, і він використовується для підвищення ефективності. Останні процесори Intel надзвичайно добре підходять і для перекладачів (хоча ви, звичайно, знайдете орієнтири, щоб довести все, що хочете довести).
Луань

Відповіді:


79

Логіка полягає в тому, що байт-код JVM набагато простіше, ніж вихідний код Java.

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

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

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

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


7
Деякі з інших ранніх спроб розповсюдження аплетів, наприклад SafeTCL, насправді поширювали вихідний код. Використання простого і чітко визначеного байтового коду Java робить перевірку програми набагато простенішою, і це була складна проблема, яку вирішували. Байт-коди, такі як p-код, вже були відомими як частина рішення проблеми переносимості (і ANDF, ймовірно, розроблявся на той час).
Toby Speight

9
Точно. Часи запуску Java вже є певною проблемою через крок машинного коду. Запустіть javac на своєму (нетривіальному) проекті, а потім уявіть, що ви робите весь цей машинний код Java -> при кожному запуску.
Пол Дрейпер

24
Це має ще одну величезну перевагу: якщо колись ми всі хочемо перейти на нову гіпотетичну нову мову - назвемо її "Скала" - нам потрібно написати лише один компілятор байт-коду Scala ->, а не десятки машинного коду Scala -> компілятори. Як бонус, ми отримуємо безкоштовно всі оптимізовані для платформи JVM платформи.
BlueRaja - Danny Pflughoeft

8
Деякі речі все ще не можливі в байтовому коді JVM, наприклад оптимізація хвостових викликів. Я пам'ятаю, це сильно компрометує функціональну мову, яка збирається в JVM.
JDługosz

8
@ JDługosz вірно: JVM, на жаль, накладає досить деякі обмеження / дизайнерські ідіоми, які, хоча вони можуть бути цілком природними, якщо ви походите з необхідної мови, можуть стати цілком штучною перешкодою, якщо ви хочете написати компілятор для мови, яка принципово працює. інший. Таким чином, я вважаю LLVM кращою ціллю, що стосується повторного повторного використання мови-робота-робота - вона також має обмеження, але вони більш-менш відповідають тим обмеженням, які діючі (і, можливо, певний час у майбутньому) процесори все одно мають.
близько

27

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

У випадку Java першою причиною, ймовірно, була портативність : перша версія Java була продана як "Написати один раз, запустити будь-де". Хоча ви можете досягти цього, поширюючи вихідний код і використовуючи різні компілятори для націлювання на різні платформи, це має кілька недоліків:

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

Інші переваги проміжного представництва включають:

  • оптимізація , коли шаблони можна помітити в байтовому коді і скласти до більш швидких еквівалентів, або навіть оптимізувати для особливих випадків, коли програма працює (використовуючи компілятор "JIT" або "Тільки в часі")
  • сумісність між декількома мовами в одній ВМ; це стало популярним у JVM (наприклад, Scala), і є явною метою .net рамки

1
Java також орієнтувалася на вбудовані системи. У таких системах апаратне забезпечення мало ряд обмежень пам'яті та процесора.
Laiv

Чи можна розробити компілятори таким чином, щоб вони спочатку компілювали вихідний код Java в байт-код, а потім компілювали байт-код в машинний код? Чи усуне це більшість згаданих вами недоліків?
Sher10ck

@ Sher10ck Так, AFAIK цілком можливо написати компілятор, який статично перетворює байт-код JVM в машинні інструкції для певної архітектури. Але це має сенс лише в тому випадку, якщо це покращить продуктивність достатньо, щоб перевершити або додаткові зусилля для дистриб'ютора, або додатковий час для першого використання для користувача. Вбудована система з низькою потужністю може виграти; сучасний ПК, який завантажує та запускає багато різних програм, мабуть, буде краще з добре налаштованим JIT. Я думаю, що Android йде десь у цьому напрямку, але не знаю деталей.
IMSoP

8

Здається, що вам цікаво, чому ми не просто поширюємо вихідний код. Дозвольте мені це питання: чому ми просто не поширюємо машинний код?

Очевидно, що відповідь тут полягає в тому, що Java, за задумом, не передбачає, що знає, на якій машині працює ваш код; це може бути настільний комп’ютер, суперкомп'ютер, телефон або будь-що між ними та поза нею. Java залишає місце для місцевого компілятора JVM, щоб зробити свою справу. На додаток до збільшення портативності вашого коду, це має добру перевагу, що дозволяє компілятору виконувати такі дії, як скористатись оптимізаціями для конкретних машин, якщо вони існують, або все ще виробляти принаймні робочий код, якщо вони відсутні. Такі речі, як інструкції SSE або апаратне прискорення, можна використовувати лише на машинах, які їх підтримують.

Якщо побачити в цьому світлі, міркування щодо використання байт-коду над вихідним вихідним кодом ясніше. Наближення до максимально можливої ​​мови машинної машини дозволяє нам усвідомити або частково усвідомити деякі переваги машинного коду, такі як:

  • Швидші терміни запуску, оскільки деякі з складання та аналізу вже зроблені.
  • Безпека, оскільки формат байтового коду має вбудований механізм підписання файлів розподілу (джерело може це зробити за домовленістю, але механізм для досягнення цього не є вбудованим так, як у байт-коді).

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

Крім того, байт-код дозволяє вдосконалити машинний код. Звичайно, є незалежність платформи та конкретні апаратні оптимізації, про які я згадував раніше, але є також такі речі, як обслуговування компілятора JVM для створення нових шляхів виконання зі старого коду. Це може бути виправлено проблеми безпеки або виявлено нові оптимізації або скористатися новими інструкціями щодо обладнання. На практиці рідко можна побачити великі зміни таким чином, тому що це може викривати помилок, але це можливо, і це те, що постійно відбувається невеликими способами.


8

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

Компілятори взагалі

Розглянемо спочатку загальне питання: чому компілятор використовує якесь проміжне подання у процесі компіляції вихідного коду для запуску якогось конкретного процесора?

Зменшення складності

Одна відповідь на це досить проста: вона перетворює задачу O (N * M) в проблему O (N + M).

Якщо нам надано N мовних джерел і M цілей, і кожен компілятор повністю незалежний, тоді нам потрібні N * M компілятори для перекладу всіх цих мов на всі ці цілі (де "target" - це щось на зразок комбінації процесор і ОС).

Якщо, однак, усі ці компілятори домовляються про загальне проміжне представлення, то ми можемо мати N передніх кінців компілятора, які переводять вихідні мови в проміжне представлення, і M задніх кінців компілятора, які переводять проміжне подання на щось, що підходить для конкретної цілі.

Сегментація проблеми

Що ще краще, вона розділяє проблему на дві більш-менш ексклюзивні сфери. Люди, які знають / піклуються про мовний дизайн, розбір і подібні речі, можуть зосередитись на передній частині компілятора, тоді як люди, які знають про набори інструкцій, дизайн процесора та подібні речі, можуть концентруватися на задньому.

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

Поділ компіляторів на передній і задній кінці з проміжним представленням для зв'язку між ними не є оригінальним для Java. Давно це було досить поширеною практикою (адже задовго до появи Яви все одно).

Моделі розподілу

Наскільки Java додала нічого нового в цьому відношенні, це було в моделі розподілу. Зокрема, незважаючи на те, що компілятори протягом тривалого часу були розділені на внутрішні та задні частини, вони зазвичай розподілялися як єдиний продукт. Наприклад, якщо ви купували компілятор Microsoft C, внутрішньо він мав "C1" і "C2", які відповідно були передній і зворотний - але ви купили лише "Microsoft C", який включав обидва штук (із "драйвером компілятора", який координував операції між цими двома). Навіть незважаючи на те, що компілятор був побудований з двох частин, звичайному розробнику, що використовує компілятор, це була лише одна річ, яка переводилася з вихідного коду на об'єктний код, між ними нічого не було видно.

Натомість Java розподілила фронт-енд у Java Development Kit та бек-енд у віртуальній машині Java. Кожен користувач Java мав резервний компілятор для націлювання на будь-яку систему, яку він використовував. Розробники Java поширювали код у проміжному форматі, тому, коли користувач завантажував його, JVM робив усе необхідне, щоб виконати його на своїй конкретній машині.

Прецеденти

Зауважте, що ця модель дистрибуції також не була абсолютно новою. Так, наприклад, P-система UCSD працювала аналогічно: передні кінці компілятора виробляли P-код, і кожна копія P-системи включала віртуальну машину, яка робила все необхідне для виконання P-коду в цій конкретній цілі 1 .

Java-байт-код

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

Сильні сторони

Java-байт-код легко створити передній компілятор. Якщо (наприклад) у вас є досить типове дерево, яке представляє вираз, його звичайно легко пройти по дереву та генерувати код досить безпосередньо з того, що ви знайдете на кожному вузлі.

Байтові коди Java досить компактні - в більшості випадків набагато компактніші, ніж вихідний код або машинний код для більшості типових процесорів (і, особливо, для більшості процесорів RISC, таких як SPARC, що Sun продається, коли вони розробляли Java). Це було особливо важливим у той час, оскільки одним із головних намірів Java було підтримка апплетів - коду, вбудованого у веб-сторінки, які завантажувались би перед виконанням - у той час, коли більшість людей зверталися до нас через модеми по телефонних лініях близько 28,8 кілобітів в секунду (хоча, звичайно, ще було досить багато людей, які користуються старими, повільнішими модемами).

Слабкі сторони

Основна слабкість байт-кодів Java полягає в тому, що вони не є особливо виразними. Хоча вони можуть висловити поняття, наявні на Java, досить добре, але вони не дуже добре допомагають висловлювати поняття, які не входять до складу Java. Так само, хоча легко виконувати байтові коди на більшості машин, набагато складніше це у спосіб, який повністю використовує будь-яку конкретну машину.

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

Підсумок

Якщо ви запитуєте про те, навіщо взагалі використовувати проміжні представлення, два основні фактори:

  1. Зменшіть задачу O (N * M) до проблеми O (N + M) і
  2. Розбийте проблему на більш керовані частини.

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

  1. Компактне представлення.
  2. Швидке та просте розшифрування та виконання.
  3. Швидкий і простий в застосуванні на найбільш поширених машинах

Можливість представляти багато мов або оптимально виконувати їх на різноманітних цілях були значно нижчими пріоритетами (якщо вони взагалі вважалися пріоритетами).


  1. То чому P-система здебільшого забута? В основному ситуація з ціноутворенням. P-система продавалася досить пристойно на Apple II, Commodore SuperPets і т. Д. Коли вийшов IBM PC, P-система була підтримуваною ОС, але MS-DOS коштував дешевше (з точки зору більшості людей, по суті, кидався безкоштовно) і швидко було доступно більше програм, оскільки саме про це писали Microsoft та IBM (серед інших).
  2. Наприклад, саме так працює Сажа .

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

2
Це чудова відповідь і хороша інформація про те, як різні проміжні представництва роблять різні вигоди. :)
IMSoP

@ninjalj: Це був справді Дуб. До того моменту, як він перетворився на Яву, я вважаю, що ідеї для телевізорів (і подібні) були відкладені (хоча я перший визнаю, що слід зробити справедливий аргумент, що Дуб і Ява - це одне і те ж).
Джеррі Труну

@TobySpeight: Так, вираз, мабуть, краще підходить туди. Спасибі.
Джеррі Труну

0

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

Це також спрощує захист захищеного авторським правом вихідного коду.


2
Байтовий код Java (і .NET) настільки легко перетворити на досить розбірливе джерело, що є продукти з назвою мангля, а іноді й іншою інформацією, щоб зробити це складніше - щось також часто робиться в JavaScript, щоб зробити його меншим, оскільки ми зараз просто можливо встановлення байтового коду для веб-браузерів.
LnxPrgr3

0

Сенс полягає в тому, що компіляція від байтового коду до машинного коду швидше, ніж інтерпретація оригінального коду до машинного коду вчасно. Але нам потрібні інтерпретації, щоб зробити наш додаток крос-платформним, оскільки ми хочемо використовувати свій оригінальний код на кожній платформі без змін та без будь-яких препаратів (компіляцій). Отже, спочатку javac компілює наш вихідний код у байт-код, потім ми можемо запустити цей байт-код у будь-якому місці, і він буде інтерпретований Java Virtual Machine для машинного коду швидше. Відповідь: це економить час.


0

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

Лише пізніше, коли стало очевидним, що продуктивність інтерпретаційних JVM все ще засмоктується, люди вкладають зусилля, щоб створити якісні компілятори, що працюють вчасно. Це дещо закрило прогалину до більш швидких мов, таких як C та C ++. (Однак деякі проблеми, пов'язані з швидкістю Java, залишаються, тому ви, ймовірно, ніколи не отримаєте середовище Java, яке працює так само добре, як і добре написаний код C.)

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


Чи не хотіли б головоломці пояснити, чому ?
cmaster

-5

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

Байт-код - це структура, яка має бути легкою для читання та виконання машиною.

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

Зауважую, що ще не було жодного прикладу. Нерозумні псевдоприклади:

//Source code
i += 1 + 5 * 2 + x;

// Byte code
i += 11, i += x
____

//Source code
i = sin(1);

// Byte code
i = 0.8414709848
_____

//Source code
i = sin(x)^2+cos(x)^2;

// Byte code (actually that one isn't true)
i = 1

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


2
Ці "приклади" байт-коду читаються людиною. Це зовсім не байт-код. Це вводить в оману, а також не стосується поставленого питання.
Wildcard

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

Читаною людиною формою є вихідний код, а не байт. Ви ілюструєте вихідний код із попередньо обчисленими виразами, а не байт-кодом. І я не пропустив, що це форум, читаючий людину: ти критикуєш інших відповідачів за те, що вони не включали жодних прикладів байтового коду, не я. Отже, ви кажете: "Я помічаю, що ще не було прикладів", а потім приступайте до надання не- прикладів, які зовсім не ілюструють байт-код. І це все ще не стосується питання. Перечитайте питання.
Wildcard
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.