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

Что такое динамическая компиляция в Quarkus?

2.7 Senior🔥 91 комментариев
#JVM и управление памятью

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

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

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

Динамическая компиляция в Quarkus

Динамическая компиляция — это процесс, при котором bytecode генерируется во время выполнения приложения, а не на этапе компиляции. В контексте Quarkus это критически важный механизм для оптимизации производительности.

Отличие от статической компиляции

// СТАТИЧЕСКАЯ компиляция (обычный Java процесс):
// 1. Исходный код (.java) → Компилятор javac
// 2. Bytecode (.class) генерируется ДО запуска
// 3. JVM интерпретирует bytecode при запуске
// 4. JIT-компилятор оптимизирует часто используемый код

public class StaticCompilation {
    public static void main(String[] args) {
        // Все .class файлы уже существуют на диске
        System.out.println("Запускаем уже скомпилированный код");
    }
}

// ДИНАМИЧЕСКАЯ компиляция (Quarkus, частично)
// 1. В runtime Quarkus генерирует код
// 2. Компилирует его в bytecode
// 3. Загружает в JVM
// 4. Выполняет оптимизированный код

Quarkus Build Time vs Runtime

Quarkus использует инновационный подход:

// Build Time (Compile Time)
// Quarkus анализирует classpath и генерирует код
public class QuarkusBuildTime {
    // Scan аннотаций
    // Генерирование кода для dependency injection
    // Создание native image metadata
    // Оптимизация reflection
}

// Runtime (Runtime)
// Динамическая генерация для специфичных случаев
public class QuarkusRuntime {
    // Bytecode generation для dynamic proxies
    // Runtime compilation для REST endpoints
    // Lazy loading optimization
}

Механизмы динамической компиляции в Quarkus

1. Code Generation (Build Time)

Quarkus генерирует код на основе аннотаций:

// Исходный код
@Path("/users")
public class UserResource {
    @Inject
    private UserService userService;
    
    @GET
    @Path("/{id}")
    public User getUser(@PathParam("id") Long id) {
        return userService.findById(id);
    }
}

// Что Quarkus генерирует (упрощённо):
public class UserResource_Quarkus {
    // Генерирует metadata для маршрутизации
    private static final String[] PATHS = {"/users", "/users/{id}"};
    
    // Генерирует DI контекст
    private UserService userService = new UserServiceImpl();
    
    // Генерирует HTTP handler
    public void handleRequest(RoutingContext ctx) {
        String id = ctx.pathParam("id");
        User user = userService.findById(Long.parseLong(id));
        ctx.response().json(user);
    }
}

2. GraalVM Native Image (Compile Time)

Динамическая компиляция в native код:

# mvn clean package -Pnative
# Quarkus компилирует Java bytecode в native машинный код
# Это происходит через GraalVM (Substrate VM)

# Результат:
# - Порядок быстрее запуск (10ms vs 1000ms)
# - Меньше памяти (50MB vs 300MB)
# - Полностью скомпилированный exe файл
public class NativeImageConfig {
    // Quarkus автоматически генерирует native-image.properties
    // Это файл конфигурации для GraalVM компилятора
    
    // Содержит информацию о:
    // - Reflection usage
    // - Serialization
    // - Resource loading
    // - Proxy classes
}

3. Bytecode Recording (Build Time)

Quarkus записывает bytecode операции:

// Например, инициализация bean'ов
public class BeanInitialization {
    // Обычный Java (JVM)
    // Bean инициализируются при запуске JVM
    // Требует reflection, что медленно
    
    // Quarkus (Build Time)
    // Quarkus анализирует все возможные инициализации
    // Генерирует bytecode для быстрой инициализации
    // Сохраняет состояние в snapshot
}

4. Runtime Proxies

Динамическая генерация proxy'ей для bean'ов:

@ApplicationScoped
public class CacheService {
    @CacheResult(cacheName = "items")
    public Item getItem(Long id) {
        return database.findById(id);
    }
}

// Quarkus генерирует proxy:
public class CacheService_Proxy extends CacheService {
    private final CacheManager cacheManager;
    
    @Override
    public Item getItem(Long id) {
        // Проверяет кеш
        if (cacheManager.contains("items", id)) {
            return cacheManager.get("items", id);
        }
        
        // Вызывает оригинальный метод
        Item result = super.getItem(id);
        
        // Сохраняет в кеш
        cacheManager.put("items", id, result);
        return result;
    }
}

JIT vs AOT в Quarkus

// JIT (Just-In-Time) — обычная JVM
public class JITCompilation {
    // 1. Код интерпретируется
    // 2. Горячий код (hot spots) компилируется в native
    // 3. Требует "разминки" (warmup)
    // 4. Быстрее со временем, но медленно стартует
}

// AOT (Ahead-Of-Time) — GraalVM Native Image
public class AOTCompilation {
    // 1. Весь код компилируется заранее (build time)
    // 2. Нет интерпретации, нет warmup
    // 3. Очень быстрый старт
    // 4. Немного медленнее на длинных операциях
}

// Quarkus гибридный подход
public class QuarkusHybridApproach {
    // Build Time:
    // - Анализирует classpath
    // - Генерирует код
    // - Может создать native image
    
    // Runtime:
    // - JVM (обычный Java процесс)
    // - Может использовать JIT для оптимизации
    // - Или запустить как native image
}

Практический пример

// 1. Исходное приложение
@QuarkusMain
public class App {
    @Inject
    UserRepository userRepository;
    
    public static void main(String[] args) {
        Quarkus.run(args);
    }
    
    @Override
    public int run(String... args) throws Exception {
        User user = userRepository.findById(1L);
        System.out.println(user);
        return 0;
    }
}

// 2. Что происходит при mvn package
// - Quarkus BUILD STEP анализирует bytecode
// - Видит @Inject на UserRepository
// - Генерирует код инъекции зависимостей
// - Создаёт ARC (Annotation to Runtime Container)
// - Сохраняет optimized metadata

// 3. При запуске java -jar app.jar
// - JVM загружает app
// - Использует pre-generated код для DI
// - Старт за 0.5-2 секунды (vs 5-10s в Spring)

// 4. При native-image
// - GraalVM компилирует весь Java bytecode в машинный код
// - Создаёт executable app
// - Старт за 10-50ms
// - Память 50MB vs 300MB обычной JVM

Плюсы динамической компиляции в Quarkus

public class QuarkusAdvantages {
    // 1. Быстрый старт (startup time)
    // - Build time code generation
    // - Минимум reflection
    // - Native image - мс вместо секунд
    
    // 2. Низкий расход памяти
    // - Removed unused code
    // - Optimized GC
    // - Native image - 50MB vs 300MB+
    
    // 3. Оптимизированный runtime
    // - Предсгенерированные proxy'и
    // - Optimized bytecode
    // - Быстрая инициализация
    
    // 4. Идеально для контейнеров и serverless
    // - Kubernetes pods быстрее поднимаются
    // - AWS Lambda холодный старт - быстрее
    // - Cloud Native applications
}

Минусы и ограничения

public class QuarkusLimitations {
    // 1. Сложнее с dynamic reflection
    // - Нужна конфигурация для GraalVM
    // - Некоторые библиотеки несовместимы
    
    // 2. Требует явной конфигурации
    // application.properties/yml
    quarkus.native.additional-build-args=-H:ReflectionConfigurationResources=reflection-config.json
    
    // 3. Build time дольше
    // - Компиляция в native image может занять минуты
    
    // 4. Меньше flexibility
    // - Динамическая загрузка классов сложнее
    // - Runtime plugins более ограничены
}

Вывод

Динамическая компиляция в Quarkus — это умная оптимизация bytecode на этапе сборки. Quarkus анализирует код, генерирует оптимизированный bytecode, удаляет неиспользуемый код и может создать native image через GraalVM. Это дает:

  • Быстрый старт (мс вместо секунд)
  • Низкий расход памяти (50MB vs 300MB)
  • Идеально для Cloud Native (Kubernetes, Serverless)
  • Меньше warming-up перед пиком производительности