Docker 容器中的 Gradle 构建占用太多内存

And*_*ost 20 java android gradle kotlin docker

我需要你的帮助来限制 Gradle 构建在构建机器上占用的内存量。我尝试构建一个包含近 100 个模块的 Android 项目,其中包括一个com.android.application模块和许多com.android.library. 单个 Gradle 命令同时执行构建、lint 和测试(如果重要的话,还包括 Robolectric 测试)。

我在基于jetbrains/teamcity-agent的 Docker 容器中运行构建。容器被限制为仅使用 64GB 主机 RAM 中的 10GB 使用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
Run Code Online (Sandbox Code Playgroud)

我用的是观察某些情况下的内存使用情况top,我注意到一个单一的java过程(在摇篮守护进程),同时慢慢占据了8GBgradle.properties不得不org.gradle.jvmargs=-Xmx4g因此它的方式太多了,比我期望的那样。

我尝试了几种方法来配置 Gradle 以某种方式限制内存使用,但我失败了。我尝试了以下设置是各种组合:

  1. 我发现 JVM 不知道 Docker 内存限制并且它需要,-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap所以我添加了它并删除了Xmx4g. 没有帮助。使用Xmx4g几乎相同。
  2. 我找到了,-XX:MaxRAMFraction但我认为它没有太大变化。
  3. 我试过了,Xmx2g但它OutOfMemoryError: GC overhead limit exceeded在某个时候抛出失败了。
  4. 我尝试-XX:MaxPermSize=1g -XX:MaxMetaspaceSize=1g将构建挂起并抛出,OutOfMemoryError: Metaspace所以我将它们增加到2g不限制整体内存使用量的程度,我认为。
  5. 我尝试将工人数量限制为 4 和 1。
  6. 我发现 Kotlin 编译器可以使用不同的执行策略:-Dkotlin.compiler.execution.strategy="in-process". 我想这会有所帮助,因为消耗内存的进程较少,但守护进程仍占用超过 8GB 的​​空间。
  7. 使用GRADLE_OPTSset to-Dorg.gradle.jvmargs="-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=10" -Dkotlin.compiler.execution.strategy="in-process"似乎稍微限制了内存(守护进程占用了大约 4.5GB,所以它看起来很有希望)但它只是用Expiring Daemon because JVM heap space is exhausted. 然后我会尝试另一个分数。我想 4 是默认值,对吧?

我也搞不清楚的参数是如何传递到摇篮守护VS摇篮CLI所以我尝试了一些不同的组合(但不是每一个可能的组合,所以我可以很容易错过的东西)两者gradle.propertiesJAVA_OPTSGRADLE_OPTSorg.gradle.jvmargs里面。能够以某种方式打印守护程序使用的实际 JVM 参数会很有帮助。我试图弄清楚的唯一方法是使用ps -x和检查传递给java进程的参数。

我应该提到我使用--no-daemonoption 和 Gradle 6.2.2尝试了所有这些东西。我现在正在试用 Gradle 6.3,但我不希望它有帮助。

如果我在具有 16GB RAM 的 MacBook 上使用 Android Studio 或 Gradle 命令在本地运行构建,它永远不会因内存限制问题而失败。此问题仅发生在构建服务器上。

我不知道接下来要做什么来限制构建过程使用的内存量。我还没有尝试过的唯一剩下的想法是:

  1. 在 Gradle 任务中设置限制,例如tasks.withType(JavaCompile) { ... }. 那会有帮助吗?
  2. 删除我使用的不太相关的 Gradle 插件,例如 com.autonomousapps.dependency-analysis

不幸的是,所有测试都非常耗时,因为根据条件,单个构建可能需要长达 30-60 分钟的时间来执行,所以我想避免盲目测试。

我做错了吗?还有其他选项可以限制内存吗?有没有办法分析一下是什么占用了这么多内存?是否可以在构建过程中强制 GC?我应该问一个不同的问题吗?

小智 1

接收操作系统内存更新,并在每次任务完成时请求比确认可用的内存更多的内存,以便守护进程停止。

import org.gradle.process.internal.health.memory.OsMemoryStatus
import org.gradle.process.internal.health.memory.OsMemoryStatusListener
import org.gradle.process.internal.health.memory.MemoryManagertask 

task expireWorkers {
    doFirst {
        long freeMemory = 0
        def memoryManager = services.get(MemoryManager.class)
        gradle.addListener(new TaskExecutionListener() {
            void beforeExecute(Task task) {
            }
            void afterExecute(Task task, TaskState state) {
                println "Freeing up memory"
                memoryManager.requestFreeMemory(freeMemory * 2)
            }
        })
        memoryManager.addListener(new OsMemoryStatusListener() {
            void onOsMemoryStatus(OsMemoryStatus osMemoryStatus) {
                freeMemory = osMemoryStatus.freePhysicalMemory
            }
        })
    }
}
Run Code Online (Sandbox Code Playgroud)