Fixing "Cannot allocate memory" (ENOMEM): Overcommit and Swap

Fixing "Cannot allocate memory" (ENOMEM): Overcommit and Swap

What Is "Cannot allocate memory"?

Conclusion: A memory-allocation system call (malloc/brk/mmap/fork) was refused by the kernel and returned ENOMEM. The process is not killed — only the allocation fails. It is different from the OOM killer, and it can appear even when free shows headroom. The cause is not always low RAM; it is often a "limit" such as overcommit accounting, ulimit -v, or max_map_count.

Cannot allocate memory is the standard message for errno ENOMEM. It shows up in app logs, dmesg, or 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)

The key point is that this behaves differently from a forced kill by the OOM killer. The OOM killer kills a process after memory is exhausted and leaves a log. ENOMEM simply refuses the request at the moment of allocation, and the process stays alive and receives an error. That is why you can see Cannot allocate memory with no Out of memory: Killed process in dmesg.

Aspect Cannot allocate memory (ENOMEM) OOM killer
When it happens At the allocation syscall After physical memory is gone
Process Survives (gets an error) Force-killed with SIGKILL
dmesg log None Out of memory: Killed process
Main cause Overcommit accounting / limits Exhausted RAM + swap

Assumptions (target environment)

  • OS: Ubuntu / general Linux
  • Symptom: malloc/fork/mmap fails with Cannot allocate memory
  • Includes cases where free appears to have headroom
  • You can read /proc/meminfo / ulimit / sysctl (permanent settings require sudo)

Why Does It Appear When RAM Is Free? (How Overcommit Works)

Conclusion: Linux accounts for how much memory it has "committed" (promised) on allocation. Under vm.overcommit_memory=2 (strict mode), the moment the total commit exceeds CommitLimit, it returns ENOMEM. This "total promised amount" (Committed_AS) is separate from free physical memory, so allocation can be refused even with RAM to spare.

When a process allocates memory, real pages are mapped only on first write (demand paging). What the kernel tracks at allocation time is the "amount promised for future use" — the commit. vm.overcommit_memory decides how far that commit is allowed, in three modes:

Mode Value Behavior
0 default Heuristic. Refuses only obviously excessive allocations
1 - Always allow. malloc almost never returns NULL (OOM may fire later)
2 - Strict accounting. ENOMEM once total commit exceeds CommitLimit

The limit in strict mode (2) is visible in /proc/meminfo:

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

CommitLimit is the "total you may promise," and Committed_AS is "what is currently promised." As the latter approaches the former, new allocations are rejected with ENOMEM. CommitLimit is computed as:

CommitLimit = total swap + physical RAM × (vm.overcommit_ratio / 100)

The default vm.overcommit_ratio is 50. So in mode 2, by default you can only promise "swap + half of physical RAM." Even with free RAM, once this accounting limit is hit the allocation fails — the classic "free but still fails" pattern.

Even in mode 0 (default), a single oversized allocation (e.g. an mmap larger than RAM + swap) is refused by the heuristic. When you see Cannot allocate memory, first check which mode you are in with cat /proc/sys/vm/overcommit_memory.

What Do You Check First? (Where to Start)

Conclusion: Check, in order: (1) which operation failed (fork vs malloc/mmap), (2) whether dmesg shows OOM, (3) real headroom in free -h, (4) accounting headroom in grep -i commit /proc/meminfo, and (5) the process limit in ulimit -v. These five points almost fully determine whether real exhaustion, overcommit accounting, or a process limit is at play.

The first split is "is memory truly gone, or was the request refused by a limit?"

# 1. Any sign the OOM killer ran (if so, it is the real-exhaustion side)
$ dmesg -T | grep -i -E 'out of memory|killed process'

# 2. Actual physical memory / swap headroom
$ free -h

# 3. Overcommit accounting headroom (important in mode 2)
$ grep -i -E 'commitlimit|committed_as' /proc/meminfo

# 4. Virtual memory limit of this shell / process
$ ulimit -v

A quick decision table:

Observation Suspected cause Go to
free available is tiny / OOM log present Real memory exhaustion Add swap / cut memory use
Mode 2 and Committed_AS ≈ CommitLimit Overcommit accounting cap Tune overcommit
ulimit -v is not unlimited Process virtual mem limit ulimit / cgroup
Only mmap fails / process holds many areas max_map_count reached max_map_count

Read the available column of free (not the free column). available is the realistic free memory after reclaiming cache and is the primary signal of real exhaustion. See investigating memory pressure for details.

Is the Overcommit Setting the Cause? (vm.overcommit_memory)

Conclusion: If the mode is 2 and Committed_AS is pinned to CommitLimit, overcommit accounting is the culprit. Raising vm.overcommit_ratio or adding swap widens CommitLimit. But the looser the accounting, the higher the real OOM risk, so the root fix is reviewing memory usage.

Check the current mode and ratio.

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

There are two ways to widen CommitLimit: raise the ratio, or add swap.

# Raise the ratio to 80% (allow promising RAM × 80% + swap)
$ sudo sysctl -w vm.overcommit_ratio=80
$ grep -i commitlimit /proc/meminfo   # confirm it applied

Make it permanent.

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

Mode 2 is deliberately used where you "never want the OOM killer to run" (databases, embedded, etc.). Raising the ratio blindly weakens that strict-accounting protection. First find out why Committed_AS is large (oversized heap reservations, too many processes); only if it is still short should you tune the ratio or add swap.

Conversely, if your only problem is "malloc returns NULL," mode 1 (always allow) is an option, but allocations succeed only to face the OOM killer on write. The failure shape just shifts from "Cannot allocate memory" to "sudden kill," so avoid casual changes. For OOM behavior, see handling OOM killer events.

Is It a Process or User Limit? (ulimit -v / cgroup)

Conclusion: If ulimit -v (RLIMIT_AS) is not unlimited, that process's virtual address space is capped and returns ENOMEM. Under systemd, MemoryMax / cgroup limits cut off allocation the same way. The system as a whole may have plenty, but the per-process limit is what fails.

Check both the logged-in shell and the running process.

# Virtual memory limit of the current shell (KB; unlimited means no cap)
$ ulimit -v

# Check a running process's limit directly (by PID)
$ cat /proc/<pid>/limits | grep -i 'address space'
Max address space   2147483648  2147483648  bytes

If ulimit -v is smaller than what the app needs, that is the direct cause. If it runs as a systemd service, check the unit's limits too.

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

To give the service more memory, configure a drop-in.

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

If you cap ulimit -v in .bashrc or similar, every process launched from that shell inherits it. Container limits (Docker --memory) and cgroup limits cause ENOMEM the same way. If it "only happens for a certain user or service," suspect the inherited limit first.

What If mmap Returns ENOMEM? (vm.max_map_count)

Conclusion: If memory has headroom but only mmap fails with Cannot allocate memory, you have likely hit vm.max_map_count (default 65530), the limit on how many memory-map areas one process can hold. This happens with Elasticsearch, many threads, or loading many shared libraries. Raise the limit to fix it.

Confirm mmap is the cause with strace.

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

Compare the process's current map count with the limit.

# Current number of map areas
$ wc -l < /proc/<pid>/maps
# System-wide limit
$ sysctl vm.max_map_count
65530
65530

If the count is pinned to the limit, raise it.

$ 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 is the representative value Elasticsearch officially requires. The right value is workload-dependent, so first watch how the map count (/proc/<pid>/maps line count) grows and leave headroom above it. Raising it costs little memory and has minor side effects.

What If fork Is What Fails?

Conclusion: fork: Cannot allocate memory happens when the child needs a commit reservation as large as the parent, and overcommit accounting (mode 2) or real shortage cannot reserve it. Forking from a large process is the trigger. Switch to posix_spawn / vfork, or revisit overcommit and swap.

fork shares memory copy-on-write, but for accounting it tries to reserve "a commit for the parent's writable pages" on the child's behalf. If the parent holds several GB, commit can exceed CommitLimit at that instant and return ENOMEM.

$ ./big_parent
fork: Cannot allocate memory

There are three directions to fix it:

  • Design: Do not fork+exec directly from a huge process; spawn children via posix_spawn or a small helper process
  • Accounting: If overcommit is in mode 2, raise the ratio / add swap to widen CommitLimit
  • Relax: vm.overcommit_memory=1 (always allow) removes the reservation check (a trade-off against OOM risk)

For the same fork, if the error is Resource temporarily unavailable (EAGAIN) the cause is the process / thread count limit, not memory, and the fix is entirely different. For ulimit -u / TasksMax / pid_max issues, see fixing "fork: Resource temporarily unavailable". Split your path by the message tail (Cannot allocate memory vs Resource temporarily unavailable).

How Do You Fix It for Good? (Checklist)

Conclusion: Cannot allocate memory splits into two families: "real shortage" and "refused by a limit." Separate them by whether an OOM log exists, then work through overcommit accounting (CommitLimit/Committed_AS) → process limits (ulimit -v/cgroup) → max_map_count. Relaxing accounting or limits is symptomatic; the root is the balance with memory usage.

  • [ ] Checked dmesg for OOM killer logs (if present, the real-exhaustion family)?
  • [ ] Checked real headroom in free -h available?
  • [ ] Checked the current mode with cat /proc/sys/vm/overcommit_memory?
  • [ ] In mode 2, checked whether Committed_AS is near CommitLimit?
  • [ ] Checked the address space limit in ulimit -v / /proc/<pid>/limits?
  • [ ] Checked MemoryMax / LimitAS in systemd / cgroup / container?
  • [ ] For mmap failures, compared vm.max_map_count with the map count?
  • [ ] For fork failures, distinguished ENOMEM from EAGAIN?
  • [ ] Before relaxing (ratio / swap / limits), investigated the real cause of high memory use?

Next Reading