JEP 139: Enhance javac to Improve Build Speed
Доработка javac для ускорения сборки
| Ответственный | Magnus Ihse Bursie |
| Тип | Feature |
| Область | Implementation |
| Статус | Closed / Delivered |
| Выпуск | 8 |
| Компонент | tools / javac |
| Обсуждение | compiler dash dev at openjdk dot java dot net |
| Трудоёмкость | L |
| Длительность | M |
| Блокирует | JEP 138: Autoconf-Based Build System |
| Одобрен | Brian Goetz |
| Создан | 2011/09/15 20:00 |
| Обновлён | 2026/07/29 18:55 |
| Задача | 8046129 |
Аннотация
Сократить время сборки JDK и обеспечить инкрементальную сборку. Для этого компилятор Java нужно изменить так, чтобы он работал на всех доступных ядрах в одном постоянном процессе, отслеживал зависимости пакетов и классов между сборками, автоматически генерировал заголовочные файлы для native-методов и удалял файлы классов и заголовочные файлы, которые больше не нужны.
Цели
Основные цели:
- Ускорить сборку: javac должен использовать все ядра и повторно использоваться в серверном процессе.
- Упростить работу разработчиков: javac должен собирать инкрементально.
Этот проект — часть более широкой работы по улучшению инфраструктуры сборки JDK.
Улучшения javac будут внутренними и недоступны через публичный API программы запуска javac. Вместо этого новая функциональность будет размещена во внутренней программе-обёртке smart-javac (сокращённо sjavac). Когда возможности обёртки javac стабилизируются, в будущем JEP можно будет предложить перенести их в публичный API javac. Тогда улучшениями смогут воспользоваться все Java-разработчики.
Что не является целью
Этот проект касается только изменений, необходимых в javac, и новой обёртки. Он не охватывает изменения в Makefile-файлах JDK, нужные для использования этих изменений; они описаны в JEP 138: Autoconf-Based Build System.
Этот проект не будет касаться изменений в javac, нужных для ускорения генерации Javadoc.
Критерии успеха
При компиляции исходников на Java должны использоваться все ядра, а при использовании нескольких ядер сборка должна ускоряться.
Нагрузка не будет идеально сбалансирована между ядрами, и известно, что одна и та же работа будет выполняться повторно несколько раз. Но изменения, которые поддерживает этот JEP, позволят нам постепенно улучшать распределение работы между ядрами внутри javac.
Инкрементальная сборка должна перекомпилировать только изменённые пакеты и их зависимости.
После инкрементальной сборки, в ходе которой были удалены классы или native-методы, выходные каталоги должны быть чистыми, т. е. в них не должно оставаться классов или заголовочных файлов C, соответствующих удалённым исходникам.
Мотивация
Полная сборка OpenJDK неоправданно медленная. Это создаёт дополнительную нагрузку на разработчиков и системы сборки. В результате разработчики извлекают и собирают лишь часть исходного кода, поскольку продукт целиком собирается слишком долго.
Описание
Внутренняя обёртка smart-javac, вероятно, будет вызываться так:
$ java -jar sjavac.jar -classpath ... -sourcepath ... -pkg '*' \
-j all -h headerdir -d outputdir
Эта команда скомпилирует все исходные файлы, найденные в sourcepath, с любыми именами пакетов ('*'), используя все (-j) ядра. В выходном каталоге (-d) будет создан файл базы данных .javac_state, который будет содержать всю информацию, необходимую для быстрой инкрементальной компиляции с корректным удалением исчезнувших классов и заголовков C, а также с правильным отслеживанием зависимостей.
Обёртка smart-javac реализует поддержку нескольких ядер, создавая по экземпляру JavaCompiler на каждое ядро. Компилируемый исходный код разбивается на пакеты, которые случайным образом распределяются между экземплярами JavaCompiler. Если у случайно выбранного пакета есть зависимости, они будут скомпилированы автоматически, но не записаны на диск, т. е. -Ximplicit:none. Если неявно скомпилированная зависимость позже запрашивается как часть случайно выбранного пакета, неявно выполненная работа не пропадает: уже скомпилированная зависимость записывается на диск.
Поскольку при начальной компиляции неизвестно, от каких пакетов зависит пакет, случайное распределение работы — лучшее, что мы можем сделать. Поэтому java.lang.Object будет перекомпилирован столько раз, сколько имеется ядер, но записывать java.lang.Object на диск будет только один из экземпляров JavaCompiler. Достаточно много пакетов не зависят друг от друга, поэтому такая стратегия вполне работоспособна для использования нескольких ядер.
Когда мы в будущем улучшим javac (это не входит в данный JEP), всё больше работы будет общей для экземпляров JavaCompiler, и в конце концов мы придём к тому, что java.lang.Object будет компилироваться только один раз.
javah будет автоматически запускаться для каждого класса, содержащего native-методы, а сгенерированные заголовки C будут помещаться в каталог заголовков (-h). Новая аннотация @ForceNativeHeader используется для классов, у которых нет native-методов, но есть final static поля примитивных типов, которые нужно экспортировать в JNI.
Чтобы не перезапускать javac и не терять оптимизации, выполненные JVM, обёртка smart-javac поддерживает параметр -server. Этот параметр запускает фоновый сервер javac, и каждый последующий вызов обёртки smart-javac, ссылающийся на тот же portfile, будет повторно использовать этот сервер.
Аргументы -server:
portfile= где хранить используемый TCP-портlogfile= где хранить вывод javac, по умолчанию portfile+".logfile"stdouterrfile= где хранить вывод сервера, по умолчанию portfile+".stdouterr"javac= путь к javac, который запускает сервер; пробелы и запятые заменяются на%20и%2C.
Пример:
-server:portfile=/tmp/jdk.port,javac=/usr/local/bin/java%20\
-jar%20/tmp/openjdk/langtools/dist/lib/bootstrap/sjavac.jar
Поскольку сейчас javac не может разделять состояние между параллельными компиляциями, каждое дополнительное ядро будет потреблять примерно столько же памяти, сколько один вызов javac. Улучшенное разделение между ядрами будет вводиться постепенно после завершения этого JEP. Параметром -j можно ограничить число ядер и тем самым потребление памяти.
Сервер javac остаётся в памяти после завершения всех компиляций. Сервер автоматически завершит работу и освободит память и другие ресурсы после 30 секунд бездействия.
Сервер запускается от имени того же пользователя, что и обычный компилятор javac, и поэтому имеет те же привилегии и ту же возможность записи в выходной каталог сборки. В отличие от обычного компилятора javac, компиляцию можно запустить, подключившись к серверу через TCP-порт. По TCP передаётся только командная строка, а не исходный код.
Потенциальный риск безопасности состоит в том, что злоумышленник может добавить компиляцию вредоносного кода, который окажется в выходном каталоге. Чтобы снизить этот риск, мы примем несколько мер:
- Каждый раз открывать новый TCP-порт; номер порта хранится в portfile.
- Разрешать подключения только с localhost.
- Требовать, чтобы до начала любой компиляции серверу был предъявлен уникальный cookie.
Cookie — это 64-битное случайное целое число, хранящееся в portfile. У portfile обычные для временных файлов права доступа, т. е. читать и записывать его может только владелец.
Альтернативы
Вместо того чтобы сделать javac полноценно параллельным, можно было бы параллельно запускать несколько однопоточных компиляций разных независимых пакетов Java. Это не потребовало бы изменений в javac, но сделать корректные Makefile-файлы было бы намного сложнее, а ускорение было бы не таким большим.
Тестирование
В рамках проекта инфраструктуры сборки будет проверено, что результаты старой и новой систем сборки идентичны. Так мы убедимся, что обёртка smart-javac генерирует идентичный результат для одних и тех же исходных файлов.
Ни один из новых параметров не будет публичным, поэтому добавлять тесты в набор тестов javac не нужно, но более специфичные тесты для обёртки smart-javac будут добавлены в каталог тестов langtools.
Риски и допущения
Итоговый продукт некорректен
- Риск: изменения в javac приводят к сборке некорректных двоичных файлов
- План снижения риска: тщательно тестировать итоговую сборку
Старые Makefile-файлы сборки будут доступны одновременно с новыми, чтобы упростить сравнение старых и новых двоичных файлов.
Зависимости
Этот JEP не зависит ни от каких других изменений. Он служит основой для JEP 138: Autoconf-Based Build System, где эта новая возможность javac будет использоваться для ускорения сборки JDK. Новые Makefile-файлы могут работать с неизменённым javac, но без завершения этого JEP они не обеспечат нужного ускорения и поддержки инкрементальной сборки.
Влияние
- Совместимость: влияние низкое. Мы не будем добавлять в javac новые аргументы.
- Безопасность: влияние низкое. В разделе «Описание» обсуждаются аспекты безопасности, связанные с открытием сервера для заданий компиляции.
- I18n/L10n: новые возможности обёртки smart-javac, скорее всего, приведут к появлению нескольких новых сообщений; полностью переводить их мы не будем, поскольку smart-javac пока не входит ни в какой публичный API.
- Тестирование: помимо самой сборки, нужно написать отдельные тестовые сценарии для различных параметров smart-javac.