はじめに

今、このブログはSpring BootGradleプロジェクトとして運用している。

デプロイの際はGitHub Actionsなどでビルドするのではなく、サーバ上でGitHubからソースコードをpullし、gradlewを使って新しいJARファイルを作成している。

ただ、サーバのメモリが小さいため、そのまま./gradlew bootJarを実行すると、メモリ不足でビルドが止まってしまった。

そこで、低メモリ環境でGradleを使ってビルドするために調査したことや、実際に行った対処法を備忘録として残す。

検証環境

  • AlmaLinux 10.1

  • メモリ

    $ free -h
                   total        used        free      shared  buff/cache   available
    Mem:           386Mi       237Mi        20Mi         9Mi       148Mi       148Mi
    Swap:             0B          0B          0B
    

メモリ不足であることの確認方法

まず、./gradlew bootJarを実行する。 画面が固まっている状態で、freeコマンドでメモリがどうなっているかを確認する。

$ free -h
               total        used        free      shared  buff/cache   available
Mem:           386Mi       365Mi       9.4Mi         9Mi        34Mi        20Mi
Swap:             0B          0B          0B

availableがほとんど無くなっていることが分かった。

しばらく待つと、以下メッセージが出て、gradlew bootJarが失敗した。

Starting a Gradle Daemon, 1 incompatible and 1 stopped Daemons could not be reused, use --status for details

FAILURE: Build failed with an exception.

* What went wrong:
Unable to start the daemon process.
This problem might be caused by incorrect configuration of the daemon.
For example, an unrecognized jvm option is used.For more details on the daemon, please refer to https://docs.gradle.org/9.4.0/userguide/gradle_daemon.html in the Gradle documentation.
Process command line: /usr/lib/jvm/java-21-openjdk/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED -XX:MaxMetaspaceSize=384m -XX:+HeapDumpOnOutOfMemoryError -Xms256m -Xmx512m -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant -cp /home/hogehoge/.gradle/wrapper/dists/gradle-9.4.0-bin/lcvyxq3t37f6mx9miaydrrgs/gradle-9.4.0/lib/gradle-daemon-main-9.4.0.jar -javaagent:/home/hogehoge/.gradle/wrapper/dists/gradle-9.4.0-bin/lcvyxq3t37f6mx9miaydrrgs/gradle-9.4.0/lib/agents/gradle-instrumentation-agent-9.4.0.jar org.gradle.launcher.daemon.bootstrap.GradleDaemon 9.4.0
Please read the following process output to find out more:
-----------------------


* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights from a Build Scan (powered by Develocity).
> Get more help at https://help.gradle.org.

本当にメモリ不足で、失敗したかどうか、dmesgを確認する。

sudo dmesg -T | grep -i -E 'killed process|out of memory|oom'

出力結果:

[Mon May  4 14:15:07 2026] unix_chkpwd invoked oom-killer: gfp_mask=0x140cca(GFP_HIGHUSER_MOVABLE|__GFP_COMP), order=0, oom_score_adj=0
[Mon May  4 14:15:07 2026]  oom_kill_process.cold+0x8/0x8b
[Mon May  4 14:15:07 2026] [  pid  ]   uid  tgid total_vm      rss rss_anon rss_file rss_shmem pgtables_bytes swapents oom_score_adj name
[Mon May  4 14:15:07 2026] oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/user.slice/user-1000.slice/session-3.scope,task=java,pid=32466,uid=1000
[Mon May  4 14:15:07 2026] Out of memory: Killed process 32466 (java) total-vm:2181736kB, anon-rss:99496kB, file-rss:28kB, shmem-rss:0kB, UID:1000 pgtables:520kB oom_score_adj:0

最後の行で、Killed process 32466 (java)とあるので、メモリ不足でgradlewkillされたことが分かる。

対処法

デーモン無しで実行する

--no-daemonオプションを付けることで、ビルド後も常駐するGradleデーモンを使わないようにできる。 まともなサーバなら、デーモンがビルド情報をメモリに保存してくれて、高速でビルドできるようになるのだが、貧弱なサーバの場合はむしろメモリを食ってしまう。

gradleデーモンをまず、落として、--no-daemonオプションを付け実行する。

# デーモンが停止しているか確認する。
./gradlew --status
# デーモンが停止していない場合は停止させる。
./gradlew --stop
# デーモン無しでgradlewを実行する。
./gradlew --no-daemon bootJar

ただ、この場合もデーモンありの場合と同様のメッセージが出て、失敗した。

To honour the JVM settings for this build a single-use Daemon process will be forked. For more on this, please refer to https://docs.gradle.org/9.4.0/userguide/gradle_daemon.html#sec:disabling_the_daemon in the Gradle documentation.

FAILURE: Build failed with an exception.

* What went wrong:
Unable to start the daemon process.
This problem might be caused by incorrect configuration of the daemon.
For example, an unrecognized jvm option is used.For more details on the daemon, please refer to https://docs.gradle.org/9.4.0/userguide/gradle_daemon.html in the Gradle documentation.
Process command line: /usr/lib/jvm/java-21-openjdk/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED -XX:MaxMetaspaceSize=384m -XX:+HeapDumpOnOutOfMemoryError -Xms256m -Xmx512m -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant -cp /home/hogehoge/.gradle/wrapper/dists/gradle-9.4.0-bin/lcvyxq3t37f6mx9miaydrrgs/gradle-9.4.0/lib/gradle-daemon-main-9.4.0.jar -javaagent:/home/hogehoge/.gradle/wrapper/dists/gradle-9.4.0-bin/lcvyxq3t37f6mx9miaydrrgs/gradle-9.4.0/lib/agents/gradle-instrumentation-agent-9.4.0.jar org.gradle.launcher.daemon.bootstrap.GradleDaemon 9.4.0
Please read the following process output to find out more:
-----------------------


* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights from a Build Scan (powered by Develocity).
> Get more help at https://help.gradle.org.

dmesgを確認すると、前回と同様にkillされていることが分かる。

sudo dmesg -T | grep -i -E 'killed process|out of memory|oom'
[Mon May  4 14:43:32 2026] Out of memory: Killed process 32682 (java) total-vm:2252440kB, anon-rss:112136kB, file-rss:100kB, shmem-rss:0kB, UID:1000 pgtables:584kB oom_score_adj:0

今回、single-use Daemonが起動してしまった。GradleのJVM設定によっては、ビルド中だけ使われるsingle-use Daemonが起動することがあるようだ。 JAVA_OPTSGRADLE_OPTSorg.gradle.jvmargsを一致させれば、single-use Daemonを避けられるようだが、私の環境ではうまくいかなかった。 今回は低メモリ環境でビルドを通すことを優先し、ここは深追いしないことにした。

並列処理をしない

Gradleは何もオプションを付けないと、最大でCPUのプロセッサの数のワーカーを作成する。

--max-workers=1オプションを付けることで、ワーカーの数を1にすることができ、メモリ使用量を抑えることができる可能性がある。

--no-daemonオプションと--max-workers=1オプションを付けて、実行してみた。

./gradlew --no-daemon --max-workers=1 bootJar

しかし、同様エラーメッセージで今回も失敗した。

dmesgを確認すると、また、Out of memorykillされている。

sudo dmesg -T | grep -i -E 'killed process|out of memory|oom'
[Mon May  4 14:54:19 2026] Out of memory: Killed process 32803 (java) total-vm:2181736kB, anon-rss:108644kB, file-rss:84kB, shmem-rss:0kB, UID:1000 pgtables:532kB oom_score_adj:0

Gradleのヒープサイズに制限をかける

Gradle User Manualによると、JVMオプションを渡さずに実行したGradleのデフォルトのヒープサイズ、メタスペースはそれぞれ以下のようになる。

  • ヒープサイズ: 512MB
  • メタスペース: 384MB

メモリのトータルを超えているため、ヒープサイズとメタスペースのサイズを小さくして実行する。

Gradle DaemonのJVMオプションを変更するには、プロジェクトルートのgradle.propertiesorg.gradle.jvmargs を設定する。 ヒープサイズが64MB、メタスペースが48MBにし、ワーカーの数も1にして、デーモンも無しにした。

org.gradle.jvmargs=-Xmx64m -XX:MaxMetaspaceSize=48m -Dfile.encoding=UTF-8
org.gradle.workers.max=1
org.gradle.daemon=false

この設定で、./gradlew bootJarを実行する。

しかし、-Xmx64m -XX:MaxMetaspaceSize=48mまで絞っても、メモリ386MiB・swapなしの環境ではビルド中にOut of memoryが発生し、Javaプロセスがkillされた。

swap領域を作ってgradlewを実行する

もうここまで来たら、Gradleを調整してどうにかするのは諦めて、swapを作り、Gradleタスクが終わったら、swapを消す方法をとることにした。

まず、swapを作成する。

# ホームディレクトリの下に、2GBのダミーファイルを作成する。
sudo fallocate -l 2G ~/swapfile
# 権限を安全なものにする。
sudo chmod 600 ~/swapfile
# 2GBのファイルが出来ていること、権限が600になっていることを確認する。
ls -lh ~/swapfile

そして、次のコマンドで作成した~/swapfileをswap領域として使うようにする。

# swap領域として初期化する。
sudo mkswap ~/swapfile
# swapを有効にする。
sudo swapon ~/swapfile
# swapが有効になっていることを確認する。
swapon --show

この状態で、./gradlew --no-daemon --max-workers=1 bootJarを実行し、JARを作成できるかどうか確認したところ、無事エラー無くJARを作成できた。

最後に、swapを削除しておく。

# swapを無効にする。
sudo swapoff ~/swapfile
# ~/swapfileのswap領域が消えていることを確認する。
swapon --show
# swapに使ったダミーファイルを削除する。
sudo rm ~/swapfile

スクリプト化する

このブログのサーバでは次のようなシェルスクリプトを作り、一連の流れをスクリプトの実行だけですむようにしている。

#!/usr/bin/env bash
set -ueo pipefail

SWAP_FILE="<swap領域にするダミーファイルのパス>"
SWAP_SIZE="2G"
REQUIRED_KB=2097152
APP_DIR="<Gradleプロジェクトのルートディレクトリ>"

cleanup() {
  if sudo swapon --show=NAME | grep -qx "$SWAP_FILE"; then
    sudo swapoff "$SWAP_FILE"
  fi
  sudo rm -f "$SWAP_FILE"
}

trap cleanup EXIT INT TERM

AVAILABLE_KB=$(df -Pk "$(dirname "$SWAP_FILE")" | awk 'NR==2 {print $4}')

if [ -z "$AVAILABLE_KB" ]; then
  echo "空き容量の取得に失敗しました: $(dirname "$SWAP_FILE")"
  exit 1
fi

if [ "$AVAILABLE_KB" -lt "$REQUIRED_KB" ]; then
  echo "空き容量が不足しています: ${AVAILABLE_KB}KB available, ${REQUIRED_KB}KB required"
  exit 1
fi

if sudo swapon --show=NAME | grep -qx "$SWAP_FILE"; then
  echo "既に有効な swap file があります: $SWAP_FILE"
  exit 1
fi

sudo rm -f "$SWAP_FILE"
sudo fallocate -l "$SWAP_SIZE" "$SWAP_FILE"
sudo chmod 600 "$SWAP_FILE"
sudo mkswap "$SWAP_FILE"
sudo swapon "$SWAP_FILE"

cd "$APP_DIR"

./gradlew --no-daemon --max-workers=1 bootJar

おわりに

Gradleだけでどうにかしたかったが、ここまで貧弱なサーバだとswapを作るしか無いと思う。

今回、サーバ上でGradleを使いビルドする方法を書いたが、やはり、GitHub Actionsなどで、自動化する方がいいだろう。今後、このブログを自動デプロイした際にはその備忘録も書こうと思う。