Мне нужна ваша помощь, чтобы ограничить объем памяти, который собирает Gradle на сборочной машине. Я пытаюсь создать проект Android, который имеет почти 100 модулей, включая один модуль com.android.application
и много com.android.library
. Одна команда Gradle выполняет сборку, сборку и тесты (включая тесты Robolectri c, если это имеет значение).
Я запускаю сборку в Docker контейнере, основанном на jetbrains / TeamCity агент . Контейнер ограничен использованием только 10 ГБ из 64 ГБ оперативной памяти хоста с опцией mem_limit
.
Сборки недавно начали давать сбой, поскольку они занимали слишком много памяти, и один из процессов был убит хост, который я мог видеть, запустив dmesg
на хосте. Это может выглядеть так:
[3377661.066812] Task in /docker/3e5e65a5f99a27f99c234e2c3a4056d39ef33f1ee52c55c4fde42ce2f203942f killed as a result of limit of /docker/3e5e65a5f99a27f99c234e2c3a4056d39ef33f1ee52c55c4fde42ce2f203942f
[3377661.066816] memory: usage 10485760kB, limit 10485760kB, failcnt 334601998
[3377661.066817] memory+swap: usage 0kB, limit 9007199254740988kB, failcnt 0
[3377661.066818] kmem: usage 80668kB, limit 9007199254740988kB, failcnt 0
[3377661.066818] Memory cgroup stats for /docker/3e5e65a5f99a27f99c234e2c3a4056d39ef33f1ee52c55c4fde42ce2f203942f: cache:804KB rss:10404288KB rss_huge:0KB shmem:0KB mapped_file:52KB dirty:12KB writeback:0KB inactive_anon:1044356KB active_anon:9359932KB inactive_file:348KB active_file:72KB unevictable:0KB
[3377661.066826] [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name
[3377661.066919] [ 6406] 0 6406 5012 14 81920 74 0 run-agent.sh
[3377661.066930] [ 8562] 0 8562 569140 2449 319488 6762 0 java
[3377661.066931] [ 8564] 0 8564 1553 20 53248 7 0 tail
[3377661.066936] [ 9342] 0 9342 2100361 92901 2437120 36027 0 java
[3377661.067178] [31134] 0 31134 1157 17 57344 0 0 sh
[3377661.067179] [31135] 0 31135 5012 83 77824 0 0 bash
[3377661.067181] [31145] 0 31145 1233001 21887 499712 0 0 java
[3377661.067182] [31343] 0 31343 4356656 2412172 23494656 0 0 java
[3377661.067195] [13020] 0 13020 56689 39918 413696 0 0 aapt2
[3377661.067202] [32227] 0 32227 1709308 30383 565248 0 0 java
[3377661.067226] Memory cgroup out of memory: Kill process 31343 (java) score 842 or sacrifice child
[3377661.067240] Killed process 13020 (aapt2) total-vm:226756kB, anon-rss:159668kB, file-rss:4kB, shmem-rss:0kB
Я наблюдал за использованием памяти в определенном сценарии ios с использованием top
, и я заметил, что один процесс java
(демон Gradle) медленно занимал более 8 ГБ, в то время как gradle.properties
имел org.gradle.jvmargs=-Xmx4g
, так что это слишком много, чем я ожидал.
Я попробовал несколько способов настроить Gradle, чтобы каким-то образом ограничить использование памяти, но мне это не удалось. Я попробовал следующие настройки в различных комбинациях:
- Я обнаружил, что JVM не знает об ограничениях памяти Docker и ей нужно
-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
, поэтому я добавил его и удалил Xmx4g
. Не помогло Использование Xmx4g
было почти таким же. - Я нашел
-XX:MaxRAMFraction
, но я думаю, что он не сильно изменился. - Я попытался
Xmx2g
, но сбой при сборке вывел OutOfMemoryError: GC overhead limit exceeded
в какой-то момент. - Я попытался
-XX:MaxPermSize=1g -XX:MaxMetaspaceSize=1g
, который повесил сборку и выбрасывал OutOfMemoryError: Metaspace
, поэтому я увеличил их до 2g
, что, я думаю, не ограничивало общее использование памяти. - Я пытался ограничить число рабочих до 4 и 1.
- Я обнаружил, что компилятор Kotlin может использовать другую стратегию выполнения:
-Dkotlin.compiler.execution.strategy="in-process"
. Я думаю, это могло бы немного помочь, потому что было меньше процессов, потребляющих память, но процесс-демон все еще занимал более 8 ГБ. - Использование
GRADLE_OPTS
с -Dorg.gradle.jvmargs="-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=10" -Dkotlin.compiler.execution.strategy="in-process"
, казалось, немного ограничивало память (демон потребовалось около 4,5 ГБ, так что это выглядело многообещающе) но просто повесил сборку с Expiring Daemon because JVM heap space is exhausted
Я попробую другую фракцию тогда. Я полагаю, что 4 - это значение по умолчанию, верно?
Я также запутался, как параметры передаются в демон Gradle и Gradle CLI, поэтому я попробовал несколько различных комбинаций (но не все возможные комбинации, чтобы я мог легко что-то пропустить) из gradle.properties
, JAVA_OPTS
и GRADLE_OPTS
с org.gradle.jvmargs
внутри. Было бы полезно как-то распечатать фактические аргументы JVM, используемые демоном. Единственный способ, которым я пытался это выяснить, - это использовать ps -x
и проверять аргументы, передаваемые процессам java
.
Я должен упомянуть, что я пробовал все эти вещи, используя опцию --no-daemon
и Gradle 6.2 0,2. Сейчас я пробую Gradle 6.3, но не ожидаю, что это поможет.
Если я запускаю сборку локально, используя команду Android Studio или Gradle на MacBook с 16 ГБ ОЗУ, это никогда не дает сбой из-за проблемы с ограничением памяти. Эта проблема возникает только на сервере сборки.
Я понятия не имею, что делать дальше, чтобы ограничить объем памяти, используемый процессом сборки. Мои единственные оставшиеся идеи, которые я еще не испробовал:
- Установка ограничений в задачах Gradle, например,
tasks.withType(JavaCompile) { ... }
. Поможет ли это? - Удаление менее значимых плагинов Gradle, которые я использую, например,
com.autonomousapps.dependency-analysis
К сожалению, все тесты очень трудоемки, потому что одна сборка может занять столько же времени, сколько и 30-60 минут, чтобы выполнить в зависимости от условий, поэтому я хотел бы избежать слепого тестирования.
Я делаю все это неправильно? Есть ли другие варианты ограничения памяти? Есть ли способ проанализировать, что занимает столько памяти? Можно ли форсировать G C в процессе сборки? Должен ли я задать другой вопрос вместо этого?