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

2022-09-01 06:16:37

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

我在一个基于 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

我正在观察某些场景中的内存使用情况,我注意到单个进程(Gradle守护程序)正在慢慢占用超过8GB,所以它比我预期的要多。topjavagradle.propertiesorg.gradle.jvmargs=-Xmx4g

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

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

我也对如何将参数传递到Gradle守护进程与Gradle CLI感到困惑,所以我尝试了两者的不同组合(但不是每个可能的组合,所以我很容易错过一些东西),以及内部。能够以某种方式打印守护程序使用的实际 JVM 参数会很有帮助。我试图弄清楚的唯一方法是使用和检查传递给进程的参数。gradle.propertiesJAVA_OPTSGRADLE_OPTSorg.gradle.jvmargsps -xjava

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

如果我在具有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
            }
        })
    }
}

答案 2

推荐