Android: GitHub Actions で Gradle build daemon disappeared unexpectedly が発生するときの対処

GitHub Actions で Android Gradle プロジェクトをビルドするとき、プロジェクトが大きくなってくると Gradle daemon がクラッシュすることがあります。

メモリ不足により OOM (Out Of Memory) で Gradle Daemon プロセスが Kill されると以下のエラーメッセージが表示されます。

```shell
FAILURE: Build failed with an exception.

* What went wrong:
Gradle build daemon disappeared unexpectedly (it may have been killed or may have crashed)
```

## Gradle Daemon のメモリ使用量の調査

Gradle Daemon が必要とするメモリ使用量の調査方法を以下の記事にまとめました。合わせて確認してください。

https://qiita.com/irgaly/items/c1acb9e06d95a471ae49

## 対応: メモリサイズの調整

Gradle Daemon が使用するメモリサイズを調整します。GitHub Actions の workflow の `GRADLE_OPTS` 環境変数に `-Dorg.gradle.jvmargs` を渡します。

`.github/workflows/{workflow name}.yml`

```yml
...
jobs:
  build:
    runs-on: ubuntu-latest
    env:
      GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx4g -XX:MaxMetaspaceSize=2g -Dkotlin.daemon.jvm.options=-Xmx1500m"
    steps:
...
```

または gradle.properties へ org.gradle.jvmargs を設定します。

`gradle.properties`

```properties
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=2g -Dkotlin.daemon.jvm.options=-Xmx1500m
```

CI 向けの設定であるため、gradle.properties よりは GitHub Actions workflow への設定をおすすめします。

## 解説

Kotlin で構成された Android Gradle Project のビルドには、以下の二つのプロセスが関わります。

* Gradle Daemon プロセス
* Kotlin Daemon プロセス

これらの両方のプロセスに対して、使用可能な最大メモリサイズを指定することで CI 環境のメモリを食い潰してしまわないように調整します。

`org.gradle.jvmargs` は、Gradle Daemon の起動時に JVM へ渡されるオプションを指定します。`org.gradle.jvmargs` のデフォルト値は `-Xmx512m "-XX:MaxMetaspaceSize=256m"` です。`-XX:MaxMetaspaceSize` のデフォルト値が小さすぎて Android アプリのビルドには足りていません。

https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties

`-Xmx` は Java ヒープメモリの最大サイズ、`-XX:MaxMetaspaceSize` は Metaspace 領域の最大サイズを指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17931/c2b37bb4-b8df-28cf-d776-04f2176f6946.png)

http://equj65.net/tech/java8hotspot/

Java ヒープメモリとネイティブメモリは上記記事の図がわかりやすいです。Metaspace 領域には Java class がロードされます。Gradle の場合は Gradle 本体や Gradle Plugin などが読み込まれると Metaspace 領域が圧迫されます。

Gradle のデフォルト値ではどちらも大きな Android プロジェクトをビルドするには不足しています。Java ヒープメモリの設定が大きすぎてもメモリを食い潰してしまい、小さすぎてもメモリが足りずにビルドできません。Metaspace については、もともと JVM の Metaspace 領域の最大値は非常に大きな値が設定されているものであるため、いくら大きな値を設定しても害はないと思います。

GitHub Actions Host runner のメモリサイズは Linux は 7 GB で、macOS は 14GB とあります。Android Gradle プロジェクトの規模によって必要なメモリサイズは異なるため、プロジェクトごとに最適なメモリサイズを指定してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17931/d7fcb281-4dcb-e3da-9c9e-7b5ce0ff1c30.png)

https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners

Kotlin ソースコードのビルドには Gradle Daemon からは独立した Kotlin Daemon プロセスが使用されます。

`-Dkotlin.daemon.jvm.options` は Gradle Daemon から Kotlin Daemon プロセスを起動するときに使用される JVM オプションです。これを指定しない場合は Gradle Daemon と同じ設定で Kotlin Daemon が起動されてしまいます。Kotlin Daemon は Gradle Daemon よりも必要な最大メモリサイズは小さくてもビルドが可能です。Gradle Daemon とは異なるメモリサイズを明示的に指定しましょう。

## 参考

https://i-beam.org/2019/08/15/jvm-heap-sizing/

JVM は実行環境のメモリサイズに応じてちょうど良いメモリサイズを自動で決定して起動する仕組みが整備されているのですが、Gradle が Gradle Daemon の起動デフォルトオプションに `-Xmx512m "-XX:MaxMetaspaceSize=256m"` を指定しているために、メモリが足りないという問題が発生してしまっています。Gradle もホスト OS のメモリサイズに合わせて動的なメモリサイズを指定してほしいところです。