Сьогодні пропоную вам прочитати переклад чудової статті про те, як відбувається процес компіляції в Java. Тема не нова, але про неї зазвичай пишуть або не зовсім зрозуміло, або геть коротко. А хотілося б просто і зі смаком =)
Отже Java одна з найпоширеніших строго типізованих мов програмування, вона також відома своєю надійністю та незалежністю від платформи. Що відрізняє Java від багатьох інших мов, так це її унікальний процес компіляції, який дозволяє запускати код на будь-якій машині, де встановлено віртуальну машину Java (далі JVM). Це робить Java-додатки дуже портативними та ефективними. У цій статті ми розглянемо весь процес компіляції Java, від написання вихідного коду до виконання програми на JVM. Розуміння цього процесу має важливе значення для будь-якого розробника Java, оскільки воно демістифікує те, що відбувається за лаштунками, коли ви компілюєте та запускаєте програму Java.
Покроковий процес компіляції Java
Створення вихідного коду
Першим кроком перед початком процесу компіляції в Java, є написання вихідного коду. Тут ви, як програміст, створюєте файл з розширенням ".java", що містить код програми, яку ви хочете виконати. Ось приклад простої програми на Java:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
У цьому файлі ми визначаємо клас HelloWorld і основний метод (це метод main якщо що), який друкує "Hello, World!" в вікні консолі. То ж коли вихідний код написаний, файл збережено, наступним кроком є його компіляція в байт-код.
Компіляція за допомогою javac (компілятор Java)
Процес компіляції в Java починається з написання команди javac яка являє собою виклик компілятора Java Compiler. Роль компілятора javac полягає в тому, щоб отримати вихідний код (написаний у файлах .java) і перевести його в байт-код, який зберігається у файлах з розширенням .class.
Якщо коротко то байт-код - це незалежне від платформи проміжне представлення програми, яке безпосередньо не виконується основним обладнанням.
Щоб скомпілювати файл HelloWorld.java, виконайте таку команду:
javac HelloWorld.java
Ця команда повідомляє javac прочитати файл HelloWorld.java і створити файл HelloWorld.class, що містить наш магічний байт-код. Компілятор javac виконує низку перевірок вихідного коду, щоб переконатися, що він відповідає синтаксису та правилам мови Java (це ніяк не пов'язано зі стилістикою коду чи його архітектурою, лише прості перевірки, все інше ви за бажанням можете зробити через вашу IDE). Якщо буде знайдено будь-яку помилку, компілятор зазнає збою та виведе повідомлення про помилку, вказуючи, що пішло не так. Хоча слід зазначити, що він завжди буде вказувати вам на вірний рядок з помилкою. Після успішної компіляції створюється файл .class, який тепер може виконуватися JVM.
Важливо: слід пам'ятати, що фай з вихідним кодом вашої програми повинен мати таку саму назву як і клас в якому розміщено основний метод main. Інакше процес компіляції та запуску буде досить болючим і складним якщо ви початківець.
Байт-код Java і роль JVM (віртуальної машини Java)
Ключем до філософії Java "напиши один раз, запусти будь-де" є використання байт-коду та JVM. Після компіляції вихідного коду в байт-код JVM відповідає за його виконання на будь-якому комп'ютері чи пристрої, де попередньо було встановлено JVM. Байт-код не є специфічним для будь-якої конкретної архітектури процесора, що робить програми Java незалежними від платформи (32-а або 64-и біти, чи операційна система, можете для себе це розуміти по різному, але суть буде однаковою в принципі).
Кожна платформа має власну реалізацію JVM, але всі JVM можуть інтерпретувати той самий байт-код. Коли ви запускаєте програму Java, JVM зчитує байт-код і перетворює його в машинний код, який може зрозуміти ваша операційна система та апаратне забезпечення.
Компоненти JVM, залучені до виконання
JVM є потужним компонентом середовища виконання Java. Віна відповідає за завантаження, перевірку та виконання байт-коду Java. Давайте дослідимо ключові частини JVM, які беруть участь у цьому процесі.
- Підсистема ClassLoader
ClassLoader відповідає за завантаження файлів .class у пам’ять. Він зчитує байт-код з диска та готує його до виконання JVM. Однією з найважливіших особливостей ClassLoader є можливість динамічного завантаження класів, що означає, що класи завантажуються в пам’ять по мірі їх необхідності під час виконання.
Наприклад, коли ви виконуєте команду:
java HelloWorld
ClassLoader JVM завантажує файл HelloWorld.class у пам’ять, щоб його можна було виконати. ClassLoader також обробляє пошук класів із зовнішніх бібліотек або пакетів, переглядаючи шлях до класів.
- Перевірка байт-коду
Після того як байт-код завантажується в пам'ять, він проходить процес перевірки. Засіб перевірки байт-коду перевіряє, чи відповідає байт-код специфікації мови Java і не порушує жодних правил, таких як доступ до приватних полів або методів. Цей крок забезпечує безпеку та стабільність програм Java, запобігаючи виконанню потенційно зловмисного або неправильно скомпільованого коду.
Відбувається перевірка на:
- Правильне використання типів даних.
- Правильне розгалуження та керування потоком.
- Забезпечення відсутності незаконного доступу до пам'яті або переповнення стека.
- Області даних виконання
JVM використовує кілька областей даних часу виконання для керування виконанням програм Java. Ці області відповідають за зберігання змінних, об’єктів і інформації про методи під час виконання програми. Основні області виконання включають:
- Купа (Heap): тут Java зберігає об’єкти, які динамічно розподіляються. Усі об’єкти створюються в купі.
- Стек (Stack): кожен потік у програмі Java має власний стек, де зберігаються локальні змінні та інформація про виклик методів.
- Область методів: у цій області зберігається інформація на рівні класу, включаючи дані методів і полів.
- Лічильник програм: Лічильник програм відстежує поточну інструкцію, що виконується в потоці.
- Механізм виконання
Механізм виконання - це частина JVM, яка відповідає за виконання байт-коду. Існує два основні способи обробки байт-коду механізмом виконання:
- Інтерпретація: JVM читає інструкції байт-коду одну за одною та переводить їх у машинний код на льоту. Це повільніше, але простіше.
- Компіляція Just-In-Time (JIT): Для підвищення продуктивності сучасні JVM використовують JIT-компіляцію, де байт-код компілюється в машинний код під час виконання. Це призводить до швидшого виконання, оскільки машинний код безпосередньо запускається ЦП.
Компіляція Just-In-Time (JIT).
Компілятор JIT є одним із найважливіших компонентів для оптимізації продуктивності Java. Коли JVM виявляє, що певні методи викликаються часто, вона компілює ці методи в машинний код, а не інтерпретує байт-код кожного разу. Це зменшує накладні витрати на переклад і значно прискорює виконання. Результатом є те, що після короткого періоду інтерпретації продуктивність покращується, оскільки програма працює швидше з кодом машинного рівня. І все це завдяки JIT компіляції!
Звільнення пам'яті (Garbage Collection)
Однією з головних переваг Java є її автоматична система керування пам’яттю, яку обробляє Garbage Collector (далі GC).
Збірка сміття - це процес, за допомогою якого JVM звільняє пам’ять, яка більше не використовується програмою. Коли об’єкт більше не доступний або не потрібен, GC видаляє його з купи, звільняючи пам’ять для нових об’єктів.
У Java є кілька алгоритмів збирання сміття, наприклад:
- Serial GC: простий однопотоковий колектор.
- Паралельний GC: використовує кілька потоків для прискорення збирання сміття.
- G1 GC (Garbage First): збирач сміття з низькою затримкою, який підходить для великих куп.
Збірка сміття в Java відбувається автоматично, але розробники можуть впливати на її поведінку за допомогою параметрів конфігурації.
Поширені помилки під час компіляції Java
Розробники Java часто стикаються з помилками компіляції та виконання. Серед поширених проблем:
- Синтаксичні помилки: вони трапляються, коли є помилка в синтаксисі вихідного коду, наприклад відсутність крапки з комою або дужок.
Наприклад:
System.out.println("Привіт, світ!"
Це призведе до помилки компіляції через відсутність закриваючої дужки та крапки з комою в кінці цієї строки.
- Проблеми з шляхами до класів: іноді зовнішні бібліотеки або залежності не знаходять під час компіляції чи виконання. Це може призвести до "ClassNotFoundException" або "NoClassDefFoundError".
Рішення: переконайтеся, що шлях до всіх необхідних бібліотек правильно вказано в файлах конфігурації чи вихідному коді.
- Винятки під час виконання: такі проблеми, як NullPointerException, виникають під час спроби отримати доступ до об’єкта, який не було ініціалізовано.
Ось в принципі і все. Геть майже нічого складного, то ж сподіваюсь вам сподобалось =)