「Cannot allocate memory」の対処 - overcommit と swap

「Cannot allocate memory」の対処 - overcommit と swap

「Cannot allocate memory」とは何のエラーか?

結論: メモリ確保の system call(malloc/brk/mmap/fork)がカーネルに拒否され ENOMEM が返った状態。プロセスは kill されず、確保「だけ」が失敗する。OOM killer とは別物で、free に空きがあっても出る。原因は物理メモリ不足とは限らず、overcommit 会計・ulimit -vmax_map_count など「上限」側にあることが多い。

Cannot allocate memory は errno ENOMEM の標準メッセージだ。アプリのログ・dmesgstrace などで次のように現れる。

$ ./myapp
fork: Cannot allocate memory
$ python3 -c 'x = bytearray(8*1024**3)'
MemoryError
$ strace -f ./myapp 2>&1 | grep ENOMEM
mmap(NULL, 1073741824, ...) = -1 ENOMEM (Cannot allocate memory)

ここで重要なのは、OOM killer による強制終了とは挙動が違う点だ。OOM killer は「メモリを使い切った後」にプロセスを kill してログを残す。ENOMEM は「確保しようとした瞬間」にカーネルが要求を断るだけで、プロセスは生きたままエラーを受け取る。だから dmesgOut of memory: Killed process が無いのに Cannot allocate memory が出る、という食い違いが起きる。

観点 Cannot allocate memory (ENOMEM) OOM killer
発生タイミング 確保 syscall の実行時 物理メモリ枯渇後
プロセス 生存(エラーを受け取る) SIGKILL で強制終了
dmesg ログ 残らない Out of memory: Killed process
主因 overcommit 会計・各種上限 実メモリ + swap の枯渇

前提(対象環境)

  • OS: Ubuntu / 一般的な Linux
  • 症状: malloc/fork/mmapCannot allocate memory で失敗する
  • free には空きがあるように見えるケースを含む
  • /proc/meminfo / ulimit / sysctl を参照できる前提(恒久設定は sudo 必須)

なぜ空きメモリがあるのに出るのか?(overcommit の仕組み)

結論: Linux はメモリ確保時に「確保を約束(commit)」する量を会計しており、vm.overcommit_memory=2(厳格モード)では commit 合計が CommitLimit を超えた瞬間に ENOMEM を返す。物理メモリの空きとは別の「約束済み総量(Committed_AS)」が上限に達すると、実際にはメモリが余っていても確保が断られる。

プロセスがメモリを確保しても、実際にページが割り当てられるのは初めて書き込んだ瞬間(demand paging)だ。確保の時点でカーネルが管理するのは「将来使うと約束した量」= commit である。この commit をどこまで許すかを決めるのが vm.overcommit_memory の 3 モード。

モード 挙動
0 既定 ヒューリスティック。明らかに過大な確保のみ拒否
1 - 常に許可。malloc はほぼ NULL を返さない(後で OOM killer が走りうる)
2 - 厳格会計。commit 合計が CommitLimit 超過で即 ENOMEM

厳格モード(2)の上限は /proc/meminfo で見える。

$ grep -i commit /proc/meminfo
CommitLimit:     6029308 kB
Committed_AS:    5980124 kB

CommitLimit は「約束してよい総量」、Committed_AS は「現在約束済みの総量」。後者が前者に迫ると新規確保が ENOMEM で弾かれる。CommitLimit は次式で決まる。

CommitLimit = swap 総量 + 物理メモリ × (vm.overcommit_ratio / 100)

vm.overcommit_ratio の既定は 50。つまりモード 2 では既定で「swap + 物理メモリの半分」しか約束できない。物理メモリが空いていても、この会計上限に達すれば確保は失敗する——これが「空きがあるのに出る」典型パターンだ。

モード 0(既定)でも、単発で巨大すぎる確保(例: 物理メモリ+swap を超える mmap)はヒューリスティックで拒否される。Cannot allocate memory を見たらまず cat /proc/sys/vm/overcommit_memory で今どのモードかを確認する。

まず何を確認するのか?(切り分けの起点)

結論: ①どの操作で出たか(forkmalloc/mmap か)、②dmesg に OOM が無いか、③free -h の実残量、④grep -i commit /proc/meminfo の会計残量、⑤ulimit -v のプロセス上限——をこの順で確認する。実メモリ枯渇・overcommit 会計・プロセス上限のどれが効いているかが、この 5 点でほぼ確定する。

最初に切り分けるべきは「本当にメモリが無いのか、それとも上限で断られたのか」だ。

# 1. OOM killer が走った形跡があるか(あれば実メモリ枯渇側)
$ dmesg -T | grep -i -E 'out of memory|killed process'

# 2. 実際の物理メモリ / swap 残量
$ free -h

# 3. overcommit の会計残量(モード 2 で重要)
$ grep -i -E 'commitlimit|committed_as' /proc/meminfo

# 4. このシェル / プロセスの仮想メモリ上限
$ ulimit -v

判断の早見表。

観測 疑う原因 進む先
free の available が極小 / OOM ログあり 実メモリ枯渇 swap 増設・メモリ削減
overcommit が 2 で Committed_AS ≒ CommitLimit overcommit 会計上限 overcommit 調整
ulimit -vunlimited でない プロセスの仮想メモリ上限 ulimit / cgroup
mmap だけが失敗 / 多数の領域を持つ max_map_count 到達 max_map_count

freeavailable を見ること(free 列ではない)。available はキャッシュ回収後に確保できる現実的な空き量で、実メモリ枯渇かどうかの一次指標になる。詳細はメモリ不足の調べ方を参照。

overcommit 設定が原因か?(vm.overcommit_memory)

結論: モードが 2 で Committed_ASCommitLimit に張り付いているなら overcommit 会計が犯人。vm.overcommit_ratio を上げる、または swap を足すと CommitLimit が広がる。ただし会計を緩めるほど実際の OOM リスクは上がるため、根本はメモリ使用量の見直し。

現在のモードと比率を確認する。

$ cat /proc/sys/vm/overcommit_memory
$ cat /proc/sys/vm/overcommit_ratio
2
50

CommitLimit を広げる選択肢は 2 つ。比率を上げるか、swap を足すかだ。

# 比率を 80% に(物理メモリの 80% + swap まで約束可能にする)
$ sudo sysctl -w vm.overcommit_ratio=80
$ grep -i commitlimit /proc/meminfo   # 反映を確認

恒久化する。

$ echo 'vm.overcommit_ratio = 80' | sudo tee /etc/sysctl.d/99-overcommit.conf
$ sudo sysctl --system

モード 2 は「OOM killer を絶対に走らせたくない」用途(DB・組込み等)で意図的に使われる設定だ。比率を闇雲に上げると、せっかくの厳格会計の保護が薄れる。Committed_AS がなぜ大きいのか(過大なヒープ予約・プロセス数過多)を先に調べ、それでも足りないなら比率調整・swap 増設へ進む。

逆に「malloc が NULL を返して困る」だけならモード 1(常に許可)も選択肢だが、確保は通っても書き込み時にメモリが無ければ OOM killer が走る。エラーの形が「Cannot allocate memory」から「突然 kill」へ移るだけなので、安易な変更は避ける。OOM 側の挙動はOOM killer 発動時の対処を参照。

プロセスやユーザーの上限が原因か?(ulimit -v / cgroup)

結論: ulimit -vRLIMIT_AS)が unlimited でなければ、そのプロセスの仮想アドレス空間が頭打ちになり ENOMEM を返す。systemd 配下なら MemoryMax / cgroup の上限も同じく確保を断つ。システム全体は余裕でも、プロセス単位の上限で失敗しているケース。

ログイン中のシェルと、実際に動いているプロセスの両方を見る。

# 現在のシェルの仮想メモリ上限(KB、unlimited なら無制限)
$ ulimit -v

# 既に動いているプロセスの上限を直接確認(PID 指定)
$ cat /proc/<pid>/limits | grep -i 'address space'
Max address space   2147483648  2147483648  bytes

ulimit -v がアプリの必要量より小さければ、それが直接の原因だ。systemd サービスとして動かしているなら、unit 側の上限も確認する。

$ systemctl show -p MemoryMax -p LimitAS myapp.service

サービスにメモリを与え直す場合は drop-in で設定する。

$ sudo systemctl edit myapp.service
[Service]
MemoryMax=4G
LimitAS=infinity
$ sudo systemctl daemon-reload
$ sudo systemctl restart myapp.service

ulimit -v.bashrc 等で絞っていると、そのシェルから起動した全プロセスに継承される。コンテナ(Docker の --memory)や cgroup の上限も同様に ENOMEM の原因になる。「特定ユーザー・特定サービスでだけ出る」なら、まず上限の継承元を疑う。

mmap が ENOMEM を返すなら?(vm.max_map_count)

結論: メモリには余裕があるのに mmap だけが Cannot allocate memory で失敗するなら、1 プロセスが持てるメモリマップ領域数の上限 vm.max_map_count(既定 65530)に達した可能性が高い。Elasticsearch・大量スレッド・多数の共有ライブラリ読込で起きる。上限を引き上げて対処する。

mmap が原因かは strace で確定できる。

$ strace -f -e trace=mmap ./myapp 2>&1 | grep ENOMEM
mmap(NULL, 262144, PROT_READ|PROT_WRITE, ...) = -1 ENOMEM (Cannot allocate memory)

対象プロセスの現在のマップ数と上限を比べる。

# 現在のマップ領域数
$ wc -l < /proc/<pid>/maps
# システムの上限
$ sysctl vm.max_map_count
65530
65530

数が上限に張り付いていれば、上限を引き上げる。

$ sudo sysctl -w vm.max_map_count=262144
$ echo 'vm.max_map_count = 262144' | sudo tee /etc/sysctl.d/99-max-map-count.conf
$ sudo sysctl --system

262144 は Elasticsearch などが公式に要求する代表値。値はワークロード依存なので、まず現在のマップ数(/proc/<pid>/maps の行数)の伸びを観測し、それを上回る余裕を持たせる。引き上げ自体のメモリコストはわずかで、副作用は小さい。

fork が失敗する場合は?

結論: fork: Cannot allocate memory は、子プロセス用に親と同量の commit 予約が必要になり、overcommit 会計(モード 2)や実メモリ不足で予約できないときに出る。大きなプロセスから fork する設計が引き金。posix_spawn / vfork への置換、または overcommit・swap の見直しで対処する。

fork は copy-on-write でメモリを共有するが、会計上は「親の書き込み可能ページぶんの commit」を子のために予約しようとする。親が数 GB を抱えていると、その瞬間に commit が CommitLimit を超えて ENOMEM になる。

$ ./big_parent
fork: Cannot allocate memory

対処の方向は 3 つ。

  • 設計: 巨大プロセスから直接 fork+exec せず、posix_spawn や小さなヘルパープロセス経由で子を起こす
  • 会計: overcommit がモード 2 なら 比率引き上げ・swap 増設CommitLimit を広げる
  • 緩和: vm.overcommit_memory=1(常に許可)で予約チェックを外す(OOM リスクとのトレードオフ)

同じ fork でも、エラーが Resource temporarily unavailableEAGAIN)ならメモリではなくプロセス数・スレッド数の上限が原因で、対処が全く異なる。ulimit -u / TasksMax / pid_max 側の問題は「fork: Resource temporarily unavailable」の対処を参照。メッセージの末尾(Cannot allocate memoryResource temporarily unavailable か)で進む先を分けること。

恒久対処はどう進めるか?(チェックリスト)

結論: Cannot allocate memory は「実メモリ不足」と「上限で断られた」の 2 系統に大別できる。OOM ログの有無で系統を分け、overcommit 会計(CommitLimit/Committed_AS)→ プロセス上限(ulimit -v/cgroup)→ max_map_count の順に潰す。会計や上限の緩和は対症療法であり、根本はメモリ使用量とのバランス。

  • [ ] dmesg で OOM killer のログを確認したか(あれば実メモリ枯渇系統)
  • [ ] free -havailable で実残量を確認したか
  • [ ] cat /proc/sys/vm/overcommit_memory で現在のモードを確認したか
  • [ ] モード 2 なら Committed_ASCommitLimit に迫っていないか確認したか
  • [ ] ulimit -v / /proc/<pid>/limits の address space 上限を確認したか
  • [ ] systemd / cgroup / コンテナの MemoryMaxLimitAS を確認したか
  • [ ] mmap 失敗なら vm.max_map_count とマップ数を比べたか
  • [ ] fork 失敗なら errno が ENOMEMEAGAIN か切り分けたか
  • [ ] 緩和(比率・swap・上限引き上げ)の前に、過大なメモリ使用の真因を調べたか

次に読む