JVM / Android Runtime
Исходный код Java и Kotlin можно использовать для Android, но Android-приложение не работает внутри обычной desktop/server JVM. Инструменты сборки создают DEX-байткод, а Android Runtime (ART) выполняет его с помощью интерпретации и скомпилированного машинного кода.
От исходного кода до DEX
Упрощённая цепочка сборки Android выглядит так:
Исходный код Java/Kotlin
↓ javac / компилятор Kotlin
JVM-байткод (файлы .class)
↓ D8 или R8 + desugaring
DEX-байткод (файлы .dex)
↓ упаковка
APK, установленный на устройстве
↓ ART
интерпретируемый или скомпилированный машинный код
Java-библиотеки обычно поставляются как JAR или AAR с файлами .class. Во время Android-сборки D8 преобразует JVM-байткод в DEX. В оптимизированных сборках R8 дополнительно может сокращать, оптимизировать и обфусцировать код перед созданием DEX.
Android App Bundle (.aab) - формат публикации, а не файл, который ART исполняет напрямую. Google Play создаёт из него APK для конкретного устройства; установленные APK содержат DEX и ресурсы, используемые во время выполнения.
Один DEX имеет ограничения на количество ссылок, включая известный предел в 65 536 ссылок на методы. Приложения, превышающие его, используют несколько DEX-файлов. Современные инструменты Android автоматически поддерживают multidex при minSdk 21 и выше; старым версиям нужна legacy multidex support.
JDK, уровень языка и Android API
Три часто смешиваемые версии выполняют разные роли:
- Gradle JDK запускает Gradle и Android Gradle Plugin во время сборки.
- Целевые версии языка и байткода Java/Kotlin определяют доступный синтаксис и уровень JVM-байткода компилятора.
compileSdkиminSdkопределяют Android API, видимые при компиляции и доступные на устройствах.
Использование новой JDK для сборки не делает все её API доступными в Android. Android предоставляет собственные Java-совместимые основные библиотеки. Библиотека должна использовать API, поддерживаемые целевыми версиями Android или доступные через поддерживаемые backport/desugaring-механизмы.
Desugaring
Во время компиляции D8/R8 desugaring преобразует новые языковые конструкции в формы, понятные старым Android runtime. Core library desugaring также может упаковать реализации некоторых новых Java API и перенаправить вызовы к ним.
Desugaring - не универсальный слой совместимости с JVM: он поддерживает конкретные возможности и API. При использовании Java API всегда проверяйте документацию Android и требования к minSdk.
Модель выполнения ART
Интерпретация, JIT и AOT
ART выполняет DEX с помощью гибридного подхода:
- Интерпретация позволяет начать выполнение без предварительной компиляции всего кода.
- JIT (Just-In-Time) компилирует часто выполняемый код во время работы приложения и собирает профиль.
- AOT (Ahead-Of-Time) компилирует выбранный код до выполнения, в том числе с учётом профиля во время установки или фоновой оптимизации устройства.
Конкретная стратегия зависит от версии Android и состояния устройства. Она балансирует скорость запуска, место для скомпилированного кода, расход памяти, время установки и производительность после прогрева. Поэтому современный ART нельзя корректно назвать только интерпретатором, JIT или AOT runtime.
Baseline Profiles заранее сообщают ART вероятные горячие участки, чтобы выбранный код можно было скомпилировать до того, как его выполнят реальные пользователи. Они дополняют runtime-профили, но не заменяют JIT и не гарантируют AOT-компиляцию всего приложения.
Процессы, Zygote и память
Обычно Android запускает каждое приложение в отдельном Linux-процессе с собственным экземпляром ART и изолированным UID. Компоненты одного приложения обычно разделяют процесс, но Android может создать или завершить его в соответствии с потребностями системы; объект Application не является постоянным глобальным singleton системы.
Новые процессы приложений обычно создаются из Zygote - предварительно инициализированного процесса с классами и ресурсами фреймворка. Разделение памяти по принципу copy-on-write ускоряет запуск процессов и снижает затраты на общую память фреймворка.
ART использует сборку мусора для управляемых объектов Java/Kotlin. GC удаляет объекты, которые больше недостижимы, но не закрывает файлы, сокеты, курсоры и другие внешние ресурсы. Закрывайте их детерминированно. Частота аллокаций, удерживаемые ссылки и паузы GC всё равно могут влиять на кадры и отзывчивость, поэтому диагностируйте память и производительность измерениями, а не ручными вызовами System.gc().
Практические следствия
- Не рассчитывайте на поведение или API только потому, что код работает на desktop JVM.
- Разделяйте совместимость JDK сборки и совместимость API устройства.
- При включённом R8 осторожно работайте с reflection, JNI, динамической загрузкой классов и сериализацией; нужному коду могут потребоваться точные keep rules.
- Измеряйте release-сборки: расположение DEX, оптимизации R8, профили и компиляция ART могут заметно отличать их от debug-сборок.
- Для важных сценариев запуска и взаимодействия используйте Baseline Profiles и benchmarks вместо попыток напрямую управлять JIT/AOT.