【问题标题】:Gradle build in Docker container taking up too much memoryGradle 在 Docker 容器中构建占用过多内存
【发布时间】:2020-08-04 02:18:11
【问题描述】:

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

我在基于 jetbrains/teamcity-agent 的 Docker 容器中运行构建。使用 mem_limit 选项,容器只能使用 64GB 主机 RAM 中的 10GB。

构建最近开始失败,因为它们占用了太多内存,并且其中一个进程被主机杀死,我可以通过在主机上运行 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

我正在使用 top 观察某些场景中的内存使用情况,我注意到单个 java 进程(Gradle 守护进程)正在慢慢占用超过 8GB,而 gradle.propertiesorg.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_OPTS 设置为 -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 是默认值,对吧?

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

我应该提到我使用 --no-daemon 选项和 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?我应该问一个不同的问题吗?

【问题讨论】:

  • 嗨@Andrzej,你找到解决办法了吗?
  • 不,我没有,不幸的是????@AbdelghaniRoussi
  • 我也有同样的问题,如果我找到解决方案会更新你的 Q :D
  • @AndrzejZabost 你可以使用这个-XX:+PrintFlagsFinal 来检查应用到 jvm 的标志,希望这会有所帮助

标签: java android docker kotlin gradle


【解决方案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
            }
        })
    }
}

【讨论】:

    猜你喜欢
    • 2020-07-21
    • 2020-12-16
    • 2017-01-19
    • 2018-07-17
    • 1970-01-01
    • 2020-02-23
    • 2020-11-05
    • 2022-06-13
    • 1970-01-01
    相关资源
    最近更新 更多