← Назад к вопросам

Как происходит процесс компиляции Java

1.3 Junior🔥 191 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как происходит процесс компиляции Java

Этот вопрос о том, как исходный код Java (.java файл) преобразуется в байт-код (.class файл) и затем выполняется JVM. Давай разберёмся во всех этапах.

Обзор процесса

Исходный код Java (.java)
    ↓
[1] Лексический анализ
    ↓
[2] Синтаксический анализ
    ↓
[3] Семантический анализ
    ↓
[4] Оптимизация
    ↓
Байт-код Java (.class)
    ↓
ClassLoader загружает .class
    ↓
JIT компилятор оптимизирует
    ↓
Машинный код
    ↓
ЦПУ выполняет

Этап 1: Лексический анализ (Lexical Analysis)

Компилятор разбивает исходный код на токены (tokens):

публичный класс HelloWorld {
    публичный static void main(String[] args) {
        System.out.println("Hello");
    }
}

Преобразуется в токены:

PUBLIC | CLASS | IDENTIFIER(HelloWorld) | LBRACE | 
PUBLIC | STATIC | VOID | IDENTIFIER(main) | LPAREN | 
IDENTIFIER(String) | LBRACKET | RBRACKET | IDENTIFIER(args) | RPAREN | 
LBRACE | IDENTIFIER(System) | DOT | IDENTIFIER(out) | DOT | 
IDENTIFIER(println) | LPAREN | STRING("Hello") | RPAREN | SEMICOLON | ...

Этап 2: Синтаксический анализ (Syntax Analysis / Parsing)

Построение синтаксического дерева (AST — Abstract Syntax Tree):

                    ClassDeclaration
                    /     |      \
                Public  Class   Block
                        |        |
                    HelloWorld   MethodDeclaration
                                 /     |    \
                            Public  Main  Parameters
                                     |      |
                                  Block   String[]
                                  |
                            MethodCall
                            (println)

Этап 3: Семантический анализ

Проверка типов, разрешение символов, проверка имён:

public class SemanticAnalysis {
    public static void main(String[] args) {
        int x = "hello";  // ОШИБКА: несовместимые типы
        System.out.println(undefined);  // ОШИБКА: неопределённая переменная
    }
}

Компилятор проверит:

  • Существуют ли классы?
  • Совместимы ли типы?
  • Доступны ли методы?
  • Инициализированы ли переменные?

Этап 4: Генерация байт-кода

Преобразование в JVM инструкции (bytecode):

public class CompilationExample {
    public static void main(String[] args) {
        int x = 5;
        int y = 10;
        int z = x + y;
        System.out.println(z);
    }
}

Байт-код (javap -c CompilationExample):

public static void main(java.lang.String[]);
  Code:
     0: iconst_5              // загрузить константу 5
     1: istore_1              // сохранить в переменную x
     2: bipush        10       // загрузить 10
     4: istore_2              // сохранить в переменную y
     5: iload_1               // загрузить x
     6: iload_2               // загрузить y
     7: iadd                  // сложить x + y
     8: istore_3              // сохранить в переменную z
     9: getstatic     #2      // загрузить System.out
    12: iload_3               // загрузить z
    13: invokevirtual #3      // вызвать println
    16: return

Структура .class файла

.class файл структура:
┌─────────────────────────┐
│ Magic Number (CAFEBABE) │  4 байта
├─────────────────────────┤
│ Версия (major.minor)    │  4 байта
├─────────────────────────┤
│ Constant Pool           │  переменный размер
├─────────────────────────┤
│ Flags (public, final)   │  2 байта
├─────────────────────────┤
│ This Class              │  2 байта
├─────────────────────────┤
│ Super Class             │  2 байта
├─────────────────────────┤
│ Interfaces              │  переменный размер
├─────────────────────────┤
│ Fields                  │  переменный размер
├─────────────────────────┤
│ Methods                 │  переменный размер
├─────────────────────────┤
│ Attributes              │  переменный размер
└─────────────────────────┘

Пример: просмотр байт-кода

# Компиляция
javac CompilationExample.java

# Просмотр структуры class файла
javap CompilationExample

Public class CompilationExample {
  public static void main(java.lang.String[]);
}

# Просмотр байт-кода
javap -c CompilationExample

# Просмотр константного пула
javap -cp CompilationExample

# Полный дизассемблер
javap -private -verbose CompilationExample

Runtime: Загрузка и выполнение

Шаг 1: ClassLoader загружает .class файл

public class ClassLoadingExample {
    static {
        System.out.println("Класс загружается при инициализации");
    }
    
    public static void main(String[] args) {
        System.out.println("Main выполняется");
    }
}

Порядок выполнения:

  1. JVM запускается
  2. ClassLoader загружает ClassLoadingExample.class
  3. Выполняется static инициализатор
  4. Вызывается main()

Шаг 2: JIT компиляция

JVM не просто интерпретирует байт-код, она его оптимизирует:

Байт-код (.class)
    ↓
JVM Interpreter (первое выполнение - медленнее)
    ↓
Профилирование (сбор статистики)
    ↓
JIT Compiler (Just-In-Time)
    ↓
Машинный код (очень быстро!)

Этот процесс называется Tiered Compilation (начиная с Java 8).

Пример: JIT оптимизация

public class JitOptimizationExample {
    public static int computeSum(int n) {
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += i;
        }
        return sum;
    }
    
    public static void main(String[] args) {
        // Первые вызовы интерпретируются
        for (int i = 0; i < 1000; i++) {
            computeSum(10000);
        }
        // После ~10000 вызовов JIT компилирует в машинный код
        // Дальнейшие вызовы очень быстрые
        
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            computeSum(10000);
        }
        long end = System.currentTimeMillis();
        System.out.println("Время: " + (end - start) + " ms");
    }
}

Compilation Flow в деталях

// Исходный код
public class CompileFlow {
    private int value = 42;
    
    public int getValue() {
        return value;
    }
}

// Этапы компиляции:

// 1. Лексер создаёт токены
// 2. Parser создаёт AST
// 3. Symbol Table разрешает имена
// 4. Type Checker проверяет типы
// 5. Code Generator генерирует байт-код
// 6. Optimizer оптимизирует байт-код
// 7. ClassWriter пишет .class файл

Инструменты компиляции

# Стандартный компилятор
javac MyClass.java

# С опциями
javac -d bin -cp lib/* src/MyClass.java

# Компиляция с оптимизацией
javac -O MyClass.java  # (deprecated в новых версиях)

# Verbose вывод
javac -verbose MyClass.java

# Дизассемблирование
javap -c MyClass

# Java 9+: jshell (REPL)
jshell

Аннотация Процессоры (Annotation Processors)

Можно добавить кастомные шаги в процесс компиляции:

public class CustomAnnotationProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, 
                          RoundEnvironment roundEnv) {
        // Генерация кода во время компиляции
        return true;
    }
}

// Примеры: Lombok, Dagger, Protocol Buffers

Оптимизации компилятора

  1. Constant folding — вычисление констант во время компиляции
int x = 5 + 10;  // компилируется как int x = 15;
  1. Dead code elimination — удаление недостижимого кода
if (false) {
    System.out.println("This will be removed");
}
  1. Inlining — встраивание методов
public int getValue() { return 42; }
// Может быть встроено прямо в вызывающий код
  1. Loop unrolling — развёртывание циклов
for (int i = 0; i < 4; i++) { ... }
// Может быть развёрнут в 4 отдельных операции

Версии bytecode

// Версия bytecode зависит от Java версии
// javap -verbose показывает version

// Java 8:  major version 52
// Java 11: major version 55
// Java 17: major version 61
// Java 21: major version 65

Вывод

Процесс компиляции Java:

  1. Compile-time (javac):

    • Лексический анализ → Синтаксический анализ
    • Семантический анализ → Оптимизация
    • Генерация байт-кода → .class файл
  2. Runtime (JVM):

    • ClassLoader загружает .class
    • Interpreter выполняет байт-код
    • JIT компилирует в машинный код
    • CPU выполняет оптимизированный код

Этот двухуровневый подход позволяет Java быть портативной и одновременно быстрой.

Как происходит процесс компиляции Java | PrepBro