В Java 9 появится Ahead-Of-Time компиляция. Это как?

jaotc

Недавно стало окончательно известно — в Java 9 будет AOT-компиляция. Мы решили рассказать о том, как это будет, зачем это нужно, а также развеять несколько устойчивых мифов, которые сложились вокруг статической и динамической компиляции.

Какая-какая компиляция?

Статическая, или Ahead-Of-Time (AOT) компиляция — это самая обычная компиляция, которую мы привыкли видеть в языке Си. Исходный код превращается в исполняемый, и на выходе получается исполняемый файл, который можно запустить позже. На статическую компиляцию требуется дополнительное время до начала работы программы, отсюда и название. Существует ещё динамическая, или Just-In-Time (JIT) компиляция — она осуществляется прямо во время работы программы, «на лету». Именно она и используется в Java в настоящий момент.

А в Java 9 хотят заменить её на статическую?

Нет. В Java 9 планируют добавить возможность AOT-компилятор в качестве альтернативы (но не замены) существующим инструментам. Утилита будет называться jaotc. В Java 9 она гарантированно будет работать только для модуля java.base, который содержит всё необходимое для работы с объектами, потоками и структурами данных — то, без чего не может обойтись ни одно приложение. Исходя из этого, AOT-компиляцию в Java 9 всё ещё можно назвать лишь экспериментальной возможностью.

А зачем это нужно?

AOT-компиляция имеет несколько специфических преимуществ. Во-первых, это защита от декомпиляции. Байткод Java можно без особого труда декомпилировать в код на Java, и, таким образом, взломать программу (которая, например, требует лицензионный ключ). Исполняемый код же можно лишь дизассемблировать, и, думается, любой может сравнить сложность поиска каких-то деталей в коде на Java и ассемблерном коде. Обфускация же далеко не панацея, т.к. она трудно реализуема при активном использовании рефлексии и всё равно не даёт стопроцентной защиты. Во-вторых, стоит обратить внимание на время запуска приложения. Для первого запуска (так называемого «холодного старта») приложению требуется значительное количество времени. Для скомпилированного статически кода на Java стартовое время не отличается от старта аналогичной программы на Си:
aot1

То есть, получается, со статической компиляцией мы получим в Java скорость Си?

Нет. Си достигает значительной скорости за счёт многих допущений небезопасного поведения. Например, в случае выхода за границы массива в Си случится segfault (в лучшем случае), а в Java просто вылетит ArrayOutOfBoundsException. Подобные проверки не бесплатны, за них приходится платить производительностью как в случае динамической компиляции, так и в случае статической.

Неверным также является представление, что Си в целом работает быстрее Java. Так, в Java компилятору доступен весь код всех подключенных библиотек, а значит, он может оптимизировать их работу наиболее выгодным для данного приложения путём. В Си же библиотеки могут подключаться в виде машинного кода, и никакие дополнительные оптимизации для них недоступны.

Так значит, JIT для Java подходит лучше? Это ведь всё-таки динамический язык.

Такое утверждение тоже будет неверным. Оптимизации, которые производит компилятор, могут быть достаточно сложными и ресурсоёмкими, могут требовать итеративного пересчёта и анализа всей программы целиком. Зачастую в процессе оптимизации возникает такое количество дополнительных данных, что хранить их в оперативной памяти невозможно, и требуется их промежуточная запись на диск. Всего этого JIT-компилятор просто не может себе позволить. Очень заметна разница в производительности между JIT-исполняемым кодом и AOT-компилированным, например, при работе с элементами пользовательского интерфейса.

Что-то я совсем запутался. Так что же лучше?

На этот вопрос нельзя дать однозначного ответа. Для разных приложений и для разных целей выбор может быть разным. Главное, что теперь он будет. Конечно, AOT-компиляторы Java существовали и раньше, однако мало кто из них может похвастаться полноценной поддержкой свежих спецификаций Java. Теперь, когда за дело взялись специалисты из Oracle, возможно, ситуация изменится в лучшую сторону. Про AOT-компиляцию Java кода есть отличный доклад Никиты Липского, на который мы в основном и опирались при подготовке данной статьи:

Можно привести пример, в каких ситуациях статическая компиляция будет явно лучше, чем JIT?

При разработке для встраиваемых систем и мобильных платформ, скорее всего, AOT-компиляция будет лучшим выбором. Встраиваемые системы чаще всего не обладают теми же вычислительными мощностями, что и настольные компьютеры или сервера, а чем слабее железо, тем дороже динамическая компиляция. В случае с мобильными устройствами решающую роль играет другой фактор — нагрузка на аккумулятор. Динамическая компиляция требует много большего расхода энергии, чем выполнение уже скомпилированного кода. Так, для iOS политика распространения приложений вовсе запрещает любую динамическую загрузку кода, поэтому Java фактически невозможно использовать для программирования под устройства Apple.

А можно больше подробностей про то, что именно мы увидим в Java 9?

Мы увидим утилиту jaotc, которая сможет создавать нативные исполняемые файлы для Linux x64, основанные на модуле java.base.

Выглядеть это будет примерно следующим образом. Мы можем создать ELF-файл:

Затем подключить его в качестве библиотеки для исполнения другого кода:

Причём (по крайней мере, в этом релизе) для AOT-компиляции и для запуска должны использоваться одни и те же параметры:

Больше технических деталей можно узнать на сайте OpenJDK.

В статье использовано изображение из Takipi BlogПётр Соковых, транслятор двоичного кода в русский язык