「Cannot allocate memory」の対処 - overcommit と swap
「Cannot allocate memory」とは何のエラーか?
結論: メモリ確保の system call(
malloc/brk/mmap/fork)がカーネルに拒否されENOMEMが返った状態。プロセスは kill されず、確保「だけ」が失敗する。OOM killer とは別物で、freeに空きがあっても出る。原因は物理メモリ不足とは限らず、overcommit 会計・ulimit -v・max_map_countなど「上限」側にあることが多い。
Cannot allocate memory は errno ENOMEM の標準メッセージだ。アプリのログ・dmesg・strace などで次のように現れる。
$ ./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 は「確保しようとした瞬間」にカーネルが要求を断るだけで、プロセスは生きたままエラーを受け取る。だから dmesg に Out 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/mmapがCannot 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 で今どのモードかを確認する。
まず何を確認するのか?(切り分けの起点)
結論: ①どの操作で出たか(
forkかmalloc/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 -v が unlimited でない |
プロセスの仮想メモリ上限 | ulimit / cgroup |
mmap だけが失敗 / 多数の領域を持つ |
max_map_count 到達 |
max_map_count |
free の available を見ること(free 列ではない)。available はキャッシュ回収後に確保できる現実的な空き量で、実メモリ枯渇かどうかの一次指標になる。詳細はメモリ不足の調べ方を参照。
overcommit 設定が原因か?(vm.overcommit_memory)
結論: モードが 2 で
Committed_ASがCommitLimitに張り付いているなら 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 -v(RLIMIT_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 unavailable(EAGAIN)ならメモリではなくプロセス数・スレッド数の上限が原因で、対処が全く異なる。ulimit -u / TasksMax / pid_max 側の問題は「fork: Resource temporarily unavailable」の対処を参照。メッセージの末尾(Cannot allocate memory か Resource temporarily unavailable か)で進む先を分けること。
恒久対処はどう進めるか?(チェックリスト)
結論:
Cannot allocate memoryは「実メモリ不足」と「上限で断られた」の 2 系統に大別できる。OOM ログの有無で系統を分け、overcommit 会計(CommitLimit/Committed_AS)→ プロセス上限(ulimit -v/cgroup)→max_map_countの順に潰す。会計や上限の緩和は対症療法であり、根本はメモリ使用量とのバランス。
- [ ]
dmesgで OOM killer のログを確認したか(あれば実メモリ枯渇系統) - [ ]
free -hのavailableで実残量を確認したか - [ ]
cat /proc/sys/vm/overcommit_memoryで現在のモードを確認したか - [ ] モード 2 なら
Committed_ASがCommitLimitに迫っていないか確認したか - [ ]
ulimit -v//proc/<pid>/limitsの address space 上限を確認したか - [ ] systemd / cgroup / コンテナの
MemoryMax・LimitASを確認したか - [ ]
mmap失敗ならvm.max_map_countとマップ数を比べたか - [ ]
fork失敗なら errno がENOMEMかEAGAINか切り分けたか - [ ] 緩和(比率・swap・上限引き上げ)の前に、過大なメモリ使用の真因を調べたか