← Back to DevBytes

Fix 'Cannot allocate memory' Error in Linux in Production: Root Cause Analysis

What is the 'Cannot allocate memory' Error in Linux?

The Cannot allocate memory error (often appearing as ENOMEM or errno 12) is a system-level failure that occurs when a process requests memory from the operating system and the kernel refuses because it cannot satisfy the request. This is not the same as an application-level OutOfMemoryError — it is a lower-level failure returned by system calls like fork(), mmap(), brk(), or malloc() when the kernel's memory management subsystem determines that no virtual memory can be granted to the calling process.

You may encounter this error in various forms across your production logs:

Critically, this error can occur even when free -m shows available RAM. The root cause is rarely a simple case of exhausting all physical memory — it is usually tied to virtual memory limits, process limits, memory fragmentation, or kernel-enforced constraints.

Why It Matters in Production

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

In production environments, this error is particularly dangerous because it is often silent until it becomes catastrophic. A single Cannot allocate memory failure can cascade:

Unlike a straightforward OOM kill (where the kernel selects a victim process and terminates it), ENOMEM on fork or allocation often leaves the system in a zombie-like state: existing processes may continue running, but no new processes can be created. This is the classic "fork bomb" symptom — the machine is alive but entirely unresponsive to new work.

Root Cause Analysis: Identifying the Real Culprit

Root cause analysis for Cannot allocate memory requires peeling back multiple layers. The error message alone tells you that an allocation failed, but not why. You must systematically investigate the following potential causes, in order of likelihood in production environments:

1. Process / Thread Limits (ulimit / cgroup)

The single most common cause in modern production systems — especially containerized ones — is hitting a limit on the number of processes or threads, not a lack of raw memory. The fork() system call requires the kernel to allocate a new process control block (PCB), stack, and memory mappings. If the process count limit is reached, fork() fails with EAGAIN or ENOMEM even when gigabytes of RAM are free.

Check the current limits for a running process (replace <PID> with the process ID):

# Check limits of a specific process
cat /proc/<PID>/limits

# Example output:
# Max processes             4096    4096    processes
# Max open files            1024    4096    files

For system-wide limits, inspect:

# Current system-wide max PID
cat /proc/sys/kernel/pid_max

# Current number of processes
ps aux | wc -l

# Or more precisely, count tasks
cat /proc/loadavg  # Last field is running/total tasks
ls /proc | grep '^[0-9]' | wc -l

In containerized environments (Docker, Kubernetes, LXC), cgroup limits are the controlling factor. Inspect them with:

# For a container with cgroup v1
cat /sys/fs/cgroup/pids/docker/<container_id>/pids.max
cat /sys/fs/cgroup/pids/docker/<container_id>/pids.current

# For cgroup v2
cat /sys/fs/cgroup/system.slice/docker-<container_id>.scope/pids.max
cat /sys/fs/cgroup/system.slice/docker-<container_id>.scope/pids.current

If pids.current equals pids.max, no new process can be forked inside that container — and you'll get Cannot allocate memory on any fork attempt, regardless of available RAM.

2. Memory Overcommit Policy and Virtual Memory Exhaustion

Linux uses a virtual memory overcommit system controlled by vm.overcommit_memory and vm.overcommit_ratio. The kernel can promise more virtual memory to processes than physically exists, relying on the fact that processes rarely use all they request. However, when overcommit is disabled or the limit is reached, allocations fail.

# Check overcommit settings
cat /proc/sys/vm/overcommit_memory
cat /proc/sys/vm/overcommit_ratio

# 0 = heuristic overcommit (default)
# 1 = always overcommit
# 2 = strict overcommit (never commit more than ratio% of RAM + swap)

To see current virtual memory commitment:

# Check committed virtual memory vs physical RAM + swap
cat /proc/meminfo | grep -E 'CommitLimit|Committed_AS'

# Example output:
# CommitLimit:    12345678 kB
# Committed_AS:   12345000 kB  <-- close to limit!

When Committed_AS approaches CommitLimit and overcommit_memory=2, the kernel will reject further mmap() or brk() calls with ENOMEM. This is common on systems with large databases or JVM applications that pre-allocate huge heaps via mmap.

3. System-wide Memory Saturation and the OOM Killer

Sometimes the error appears just before the OOM killer activates, or on systems where OOM killer is disabled or tuned to be less aggressive. Check kernel logs for allocation failures:

# Look for kernel-level allocation failures
dmesg -T | grep -i 'allocation failure\|out of memory\|OOM'
journalctl -k | grep -i 'alloc_pages\|out of memory'

# Check current memory pressure
cat /proc/meminfo
free -h

Key fields in /proc/meminfo to watch:

grep -E '^(MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|Unevictable)' /proc/meminfo

If MemAvailable is near zero and swap is exhausted, the system is truly out of memory. But remember: MemFree being low is normal on Linux due to cache — always look at MemAvailable which accounts for reclaimable cache.

4. Memory Fragmentation and HugePages

Memory fragmentation occurs when the kernel has plenty of free memory, but cannot find a contiguous block of the requested size (order). This is especially common on long-running systems with heavy network I/O or systems using HugePages. The kernel log will reveal the allocation order that failed:

# Check kernel ring buffer for page allocation failures
dmesg | grep -E 'order:|alloc_pages'

# Example: "alloc_pages: order:4 failed" means kernel needed 
# 2^4 = 16 contiguous pages (64KB on x86_64) but couldn't find them

Check current fragmentation:

# View buddy allocator fragmentation
cat /proc/buddyinfo

# Example: Node 0, zone Normal ... 438 217 89 34 12 5 2 1 0 0 1
# Each column is a power-of-2 page block count. Many zeros in higher 
# orders = severe fragmentation

Also check HugePages configuration, as reserved but unused HugePages reduce available general-purpose memory:

cat /proc/sys/vm/nr_hugepages
cat /proc/meminfo | grep -i huge

5. Per-process Virtual Memory Limit (vm.max_map_count, RLIMIT_AS)

Even when system memory is abundant, a single process can hit its own virtual memory limits:

# Check address space limit for a process
cat /proc/<PID>/limits | grep 'Max address space'

# Check max memory map regions (important for JVM, Elasticsearch, Redis)
cat /proc/sys/vm/max_map_count
cat /proc/<PID>/maps | wc -l   # current map count for the process

If a process exceeds vm.max_map_count (default 65530), mmap() calls fail with ENOMEM. This is a notorious issue with Elasticsearch and MongoDB workloads that use many memory-mapped files.

6. Kernel Memory Leaks or Slab Cache Bloat

Kernel memory leaks (in drivers, filesystems, or kernel modules) can consume memory that free does not account for in userspace. The slab allocator caches kernel objects; excessive slab usage can starve userspace allocations:

# Check slab cache usage
cat /proc/meminfo | grep -E 'SReclaimable|SUnreclaim|Slab'
slabtop -s c   # sort by cache size, interactive

# Or snapshot:
slabtop -s c -o | head -30

If SUnreclaim (unreclaimable slab memory) grows continuously over days, you may have a kernel memory leak. This requires deeper investigation with slabtop and potentially kernel debugging.

How to Fix It: Immediate and Long-term Remediation

Once you've identified the root cause through the analysis above, apply the appropriate fix. Below are concrete remediation steps organized by root cause category.

Fix 1: Increase Process Limits (ulimit / cgroup)

For a running process (temporary, as root):

# Set max processes for a bash session
ulimit -u 8192

# Or for a specific process using prlimit (Linux)
prlimit --pid <PID> --nproc=8192:8192

Permanent system-wide via /etc/security/limits.conf:

# Add to /etc/security/limits.conf
*                soft    nproc           8192
*                hard    nproc           16384
root             soft    nproc           unlimited
root             hard    nproc           unlimited

For systemd services, set limits in the unit file:

# In /etc/systemd/system/myservice.service
[Service]
LimitNPROC=8192
LimitNOFILE=65536

For Docker containers at runtime:

# Increase pids limit when running container
docker run --pids-limit 1024 --memory 2g myimage

# In docker-compose.yml
services:
  myapp:
    pids_limit: 1024
    mem_limit: 2g

For Kubernetes pods:

# In pod spec or deployment template
spec:
  containers:
  - name: myapp
    resources:
      limits:
        memory: "2Gi"
      requests:
        memory: "1Gi"
    # Note: Kubernetes does not have a direct pids limit field in older versions
    # Use --pids-limit on the containerd/runc level or set via node allocatable

For Kubernetes nodes, ensure kubelet reserve allows enough PID space:

# kubelet configuration
# --pod-max-pids or --max-pods combined with system reserved pids

Fix 2: Tune Memory Overcommit

For most production workloads, the default heuristic overcommit (overcommit_memory=1 or 0) works well. If you are on strict mode (2) and hitting limits:

# Temporarily switch to heuristic overcommit
echo 0 > /proc/sys/vm/overcommit_memory

# Or always overcommit (use with caution)
echo 1 > /proc/sys/vm/overcommit_memory

# Permanent via sysctl
echo "vm.overcommit_memory = 0" >> /etc/sysctl.conf
sysctl -p

If you must stay on strict mode, increase the ratio:

# Set overcommit ratio to 90% of RAM (default is 50)
echo 90 > /proc/sys/vm/overcommit_ratio
echo "vm.overcommit_ratio = 90" >> /etc/sysctl.conf

Fix 3: Free Memory and Manage OOM

When memory is genuinely exhausted, you need to free it or add capacity:

# Drop page cache (non-destructive, cache will rebuild)
echo 3 > /proc/sys/vm/drop_caches
sync; echo 3 > /proc/sys/vm/drop_caches

# Identify memory hogs
ps aux --sort=-%mem | head -20

# For containers, check cgroup memory usage
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.usage_in_bytes

Enable and tune the OOM killer to be more responsive:

# Make OOM killer more aggressive (lower score for important processes)
# Adjust oom_score_adj for critical processes to -1000 (unkillable)
echo -1000 > /proc/<PID>/oom_score_adj

# Check current oom_score (higher = more likely to be killed)
cat /proc/<PID>/oom_score

For systemd services, configure OOM protection:

# In service unit file
[Service]
OOMScoreAdjust=-900

Fix 4: Resolve Memory Fragmentation

Fragmentation requires proactive measures:

# Compact memory (requires kernel 3.10+)
echo 1 > /proc/sys/vm/compact_memory

# Defragment via page migration
echo 3 > /proc/sys/vm/drop_caches  # first, drop caches
echo 1 > /proc/sys/vm/compact_memory

# Tune vm.min_free_kbytes to keep a reserve pool for high-order allocations
echo 131072 > /proc/sys/vm/min_free_kbytes   # 128MB reserve
echo "vm.min_free_kbytes = 131072" >> /etc/sysctl.conf

Reduce HugePages if they are over-allocated:

# Check current HugePages
cat /proc/sys/vm/nr_hugepages

# Reduce to zero if not needed
echo 0 > /proc/sys/vm/nr_hugepages
echo "vm.nr_hugepages = 0" >> /etc/sysctl.conf

Fix 5: Increase vm.max_map_count

For workloads like Elasticsearch, Kafka, or MongoDB that use many mmap regions:

# Check current limit
cat /proc/sys/vm/max_map_count

# Increase significantly (262144 is common for ES)
echo 262144 > /proc/sys/vm/max_map_count
echo "vm.max_map_count = 262144" >> /etc/sysctl.conf
sysctl -p

For Docker, ensure this is set on the host — containers inherit the host kernel's max_map_count.

Fix 6: Kernel Slab / Memory Leak Mitigation

If slab memory is bloated, you can drop reclaimable slab caches:

# Drop reclaimable slab (dentries, inodes)
echo 2 > /proc/sys/vm/drop_caches
sync; echo 2 > /proc/sys/vm/drop_caches

For persistent kernel leaks, you may need to identify the leaking module and reload it:

# Check top slab consumers
slabtop -s c -o | head -20

# If a specific module (e.g., a filesystem driver) is leaking, 
# you may need to unmount filesystems, rmmod the module, and reload
# This is complex and may require a maintenance window

In extreme cases, a scheduled reboot is the most reliable remediation while you work with kernel vendors on a permanent fix.

Best Practices for Prevention

Preventing Cannot allocate memory errors in production requires a defense-in-depth approach. Implement these practices across your infrastructure:

Debugging Script for Quick Diagnosis

Below is a diagnostic script you can run on a problematic production host (requires root or appropriate capabilities) to quickly surface the most likely root cause. This script is safe to run and does not modify any system state:

#!/bin/bash
# diagnose_enomem.sh - Quick root cause analysis for 'Cannot allocate memory'
# Run as: sudo bash diagnose_enomem.sh

echo "=== SYSTEM MEMORY OVERVIEW ==="
free -h
echo ""

echo "=== VIRTUAL MEMORY COMMITMENT ==="
grep -E 'CommitLimit|Committed_AS' /proc/meminfo
echo ""

echo "=== OVERCOMMIT POLICY ==="
echo "overcommit_memory: $(cat /proc/sys/vm/overcommit_memory)"
echo "overcommit_ratio: $(cat /proc/sys/vm/overcommit_ratio)%"
echo ""

echo "=== PROCESS COUNT LIMITS ==="
echo "pid_max: $(cat /proc/sys/kernel/pid_max)"
echo "current process count: $(ls /proc | grep '^[0-9]' | wc -l)"
echo ""

echo "=== MEMORY FRAGMENTATION (buddyinfo) ==="
cat /proc/buddyinfo
echo ""

echo "=== SLAB USAGE ==="
grep -E 'SReclaimable|SUnreclaim|Slab' /proc/meminfo
echo ""

echo "=== HUGEPAGES ==="
grep -i huge /proc/meminfo | grep -v '^$'
echo ""

echo "=== MAX MAP COUNT ==="
echo "vm.max_map_count: $(cat /proc/sys/vm/max_map_count)"
echo ""

echo "=== TOP 10 MEMORY CONSUMERS ==="
ps aux --sort=-%mem | head -11
echo ""

echo "=== TOP 10 PROCESS COUNT BY USER ==="
ps aux | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
echo ""

echo "=== OOM KILLER HISTORY ==="
dmesg -T 2>/dev/null | grep -i 'out of memory\|killed process' | tail -20
echo ""

echo "=== RECENT ALLOCATION FAILURES ==="
dmesg -T 2>/dev/null | grep -i 'allocation failure\|alloc_pages' | tail -10
echo ""

echo "=== CGROUP CONSTRAINTS (if present) ==="
if [ -d /sys/fs/cgroup/pids ]; then
    echo "cgroup v1 pids hierarchy found"
    find /sys/fs/cgroup/pids -name 'pids.max' 2>/dev/null | while read f; do
        max=$(cat "$f" 2>/dev/null)
        cur=$(cat "${f%max}current" 2>/dev/null)
        if [ "$max" != "max" ] && [ "$cur" != "" ]; then
            ratio=$(( cur * 100 / max ))
            if [ "$ratio" -gt 80 ]; then
                echo "WARNING: $f: current=$cur max=$max usage=$ratio%"
            fi
        fi
    done
fi

echo "=== DIAGNOSIS COMPLETE ==="
echo "Investigate the sections above for values near or at their limits."

Conclusion

The Cannot allocate memory error on Linux is a deceptive failure — its message suggests a simple lack of RAM, but the true root cause often lies in process limits, virtual memory overcommit policy, memory fragmentation, or kernel-level constraints. Effective root cause analysis requires systematically checking each layer: process counts and cgroup limits, virtual memory commitment ratios, buddy allocator fragmentation, HugePages reservations, vm.max_map_count, and slab cache consumption. By following the diagnostic methodology and applying the targeted fixes outlined in this tutorial, you can resolve the immediate failure and build preventive measures that keep your production systems resilient. The diagnostic script provided gives you a single-command snapshot to accelerate triage during incidents. Remember: in production, the goal is not just to fix the error when it occurs, but to instrument your systems so comprehensively that you detect the warning signs long before fork() ever fails.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles