← Back to DevBytes

Fix 'Cannot allocate memory' Error in Linux

Understanding the 'Cannot allocate memory' Error in Linux

The "Cannot allocate memory" error (often surfacing as ENOMEM or errno 12) is one of the most frustrating yet common issues developers and system administrators encounter on Linux systems. It can appear during process forking, memory mapping, thread creation, or even when executing simple shell commands. This tutorial will walk you through the complete diagnostic and resolution workflow.

What the Error Actually Means

At its core, this error indicates that the operating system cannot fulfill a memory allocation request. This doesn't always mean the system is completely out of physical RAM. The kernel may refuse an allocation request due to several distinct resource constraints:

Why This Error Matters

Ignoring this error leads to cascading failures: databases crash during query execution, web servers drop requests, container orchestration systems evict pods, and critical system daemons like sshd or cron may silently die. In production environments, a single ENOMEM event can trigger an outage that takes down an entire service tier. Understanding the root cause—not just the symptom—is essential for building resilient systems.

Diagnosing the Root Cause

🚀 Deploy your AI agent in 10 minutes

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

Try it free →

Step 1: Check System-Wide Memory Statistics

Start with a broad view. Use free and /proc/meminfo to understand the current state:

# Get a human-readable summary
free -h

# Detailed breakdown from the kernel
cat /proc/meminfo

# Watch memory pressure in real time (every 2 seconds)
watch -n 2 'free -h; echo "---"; cat /proc/meminfo | grep -E "MemAvailable|SwapFree|CommitLimit|Committed_AS"'

Key fields to watch in /proc/meminfo:

Step 2: Identify the Culprit Process

Find which process is consuming excessive memory or triggering the allocation failures:

# Sort processes by resident memory (RSS)
ps aux --sort=-rss | head -20

# Alternative: top in batch mode sorted by memory
top -b -n 1 -o %MEM | head -20

# Check per-process memory maps for a suspicious PID
cat /proc/PID/status | grep -E "VmRSS|VmSize|VmPeak|VmData|VmStk"
cat /proc/PID/smaps_rollup

# Count processes per user (fork bombs often cause ENOMEM)
ps aux | awk '{print $1}' | sort | uniq -c | sort -rn | head -10

Step 3: Check Process Limits (ulimit)

A process hitting its own resource ceiling will see ENOMEM even if the system has free memory:

# Check limits for a running process (replace PID)
cat /proc/PID/limits

# Common restrictive limits:
# Max processes (-u) — fork() fails with EAGAIN/ENOMEM
# Max virtual memory (-v) — mmap/malloc fail
# Max data segment (-d) — brk/sbrk fail

# Check current shell limits
ulimit -a

# For a specific running service (systemd)
systemctl show myservice.service | grep -i limit

Step 4: Inspect cgroup / Container Limits

If running inside Docker, Kubernetes, or systemd with MemoryMax, cgroup limits are the most common cause:

# For a Docker container
docker inspect CONTAINER_ID | grep -i memory

# Check cgroup memory stats directly
cat /sys/fs/cgroup/memory/docker/CONTAINER_ID/memory.limit_in_bytes
cat /sys/fs/cgroup/memory/docker/CONTAINER_ID/memory.usage_in_bytes
cat /sys/fs/cgroup/memory/docker/CONTAINER_ID/memory.events

# For systemd services with memory limits
systemctl show myservice.service | grep -E "MemoryCurrent|MemoryMax|MemoryLimit"

# Check Kubernetes pod cgroup (find the cgroup path first)
cat /sys/fs/cgroup/memory/kubepods.slice/kubepods-besteffort.slice/.../memory.limit_in_bytes

Step 5: Analyze the Kernel Overcommit Policy

The kernel's memory overcommit behavior is controlled by vm.overcommit_memory:

# Read current overcommit settings
cat /proc/sys/vm/overcommit_memory
cat /proc/sys/vm/overcommit_ratio
cat /proc/sys/vm/overcommit_kbytes

# Interpretation:
# overcommit_memory = 0: heuristic overcommit (default) — kernel estimates availability
# overcommit_memory = 1: always overcommit — malloc almost never fails
# overcommit_memory = 2: strict accounting — allocations limited to swap + ratio% of RAM

# Check the current commit limit and usage
grep -E "CommitLimit|Committed_AS" /proc/meminfo

Fixing the Error

Fix 1: Free Up Memory Immediately

The fastest relief when the system is genuinely out of memory:

# Drop page cache, dentries, and inodes (safe, reclaimable)
echo 3 > /proc/sys/vm/drop_caches

# Sync first to avoid data loss, then drop
sync; echo 3 > /proc/sys/vm/drop_caches

# Kill the largest memory consumer (careful in production!)
# Identify the biggest RSS process
ps aux --sort=-rss | head -5
kill -15 PID   # graceful first
kill -9 PID    # force if unresponsive

# If a specific service is bloated, restart it
systemctl restart postgresql.service

Fix 2: Increase Swap Space

Adding swap gives the kernel more headroom, especially important on systems with spinning disks where swap performance is acceptable for infrequently accessed pages:

# Create a 4GB swap file
dd if=/dev/zero of=/swapfile bs=1M count=4096
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

# Make it permanent
echo '/swapfile none swap sw 0 0' >> /etc/fstab

# Verify
swapon --show
free -h

Fix 3: Adjust Process ulimit Values

Raise limits for processes that legitimately need more memory:

# Temporarily for current shell
ulimit -v unlimited
ulimit -u 65535

# Permanently via limits.conf
# Add to /etc/security/limits.conf:
# myuser    soft    nproc     65535
# myuser    hard    nproc     65535
# myuser    soft    as        unlimited
# myuser    hard    as        unlimited

# For systemd services, override in the unit file or drop-in:
# /etc/systemd/system/myservice.service.d/limits.conf
[Service]
LimitNOFILE=65535
LimitNPROC=65535
LimitAS=infinity

Fix 4: Modify Kernel Overcommit Policy

If strict overcommit (mode 2) is causing premature allocation failures, adjust it:

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

# Or allow full overcommit (use cautiously)
echo 1 > /proc/sys/vm/overcommit_memory

# For permanent change, add to /etc/sysctl.conf or /etc/sysctl.d/99-overcommit.conf:
vm.overcommit_memory = 0
vm.overcommit_ratio = 50    # percentage of RAM for commit accounting in mode 2
vm.overcommit_kbytes = 0    # or set explicit kbytes

# Apply without reboot
sysctl -p /etc/sysctl.d/99-overcommit.conf

Fix 5: Tune the OOM Killer Behavior

When memory is genuinely exhausted, the Out-Of-Memory (OOM) killer selects a victim. You can influence its decisions:

# Check current OOM score of processes
cat /proc/PID/oom_score
cat /proc/PID/oom_score_adj

# Protect a critical process from being killed (negative adj)
echo -1000 > /proc/PID/oom_score_adj

# Make a process more likely to be killed (positive adj)
echo 1000 > /proc/PID/oom_score_adj

# Disable OOM killer entirely for a process (DANGER: kernel may panic)
echo -1000 > /proc/PID/oom_score_adj   # effectively immune

# Check system-wide OOM control
cat /proc/sys/vm/panic_on_oom   # 0 = OOM kills process, 1 = kernel panic

Fix 6: Resolve Memory Leaks in Application Code

If a specific process grows unbounded, the fix must happen at the application level. Here are diagnostic techniques for developers:

# Attach with strace to see failing syscalls
strace -p PID -e trace=mmap,brk,madvise -o strace_output.log

# Use valgrind for offline leak detection (restart process under valgrind)
valgrind --leak-check=full --track-origins=yes --log-file=valgrind.log ./myapp

# For Java applications, check heap usage
jstat -gcutil PID 1000 10
jmap -heap PID
jmap -histo:live PID | head -30

# For Node.js, check heap with inspector
node --inspect --expose-gc myapp.js
# Then in Chrome DevTools: Memory > Take Heap Snapshot

# For Python, use tracemalloc embedded in the app:
import tracemalloc
tracemalloc.start()
# ... after suspected leak ...
snapshot = tracemalloc.take_snapshot()
for stat in snapshot.statistics('lineno')[:10]:
    print(stat)

Fix 7: Container-Specific Solutions

For Docker/Kubernetes workloads:

# Docker: Increase container memory limit
docker update --memory 2g --memory-swap 3g CONTAINER_ID

# Or recreate with higher limits
docker run -d --memory 2g --memory-swap 3g --name myapp myimage

# Kubernetes: Adjust resource limits in the pod spec
# Edit the deployment or pod YAML:
resources:
  limits:
    memory: "2Gi"
  requests:
    memory: "1Gi"

# Apply the change
kubectl apply -f deployment.yaml

# Check if OOMKilled events occurred
kubectl describe pod POD_NAME | grep -i oom
kubectl get events --field-selector reason=OOMKilling

Fix 8: Address Fork Bomb Scenarios

A fork bomb (rapid uncontrolled process creation) exhausts kernel memory for process tables, not necessarily user memory:

# Detect a fork bomb: look for an explosion of process count
ps aux | wc -l
# Compare with normal baseline

# Limit processes per user in /etc/security/limits.conf
*               hard    nproc           4096

# systemd also provides process limits per service
# In the unit file:
[Service]
TasksMax=512

# Use cgroups pids controller to limit forks
echo 512 > /sys/fs/cgroup/pids/user.slice/user-1000.slice/pids.max

Best Practices for Prevention

1. Implement Proactive Monitoring

Set up alerts before ENOMEM actually occurs:

# Script for Nagios / Sensu / custom monitoring
#!/bin/bash
AVAILABLE=$(grep MemAvailable /proc/meminfo | awk '{print $2}')
THRESHOLD_MB=512
THRESHOLD_KB=$((THRESHOLD_MB * 1024))

if [ "$AVAILABLE" -lt "$THRESHOLD_KB" ]; then
    echo "CRITICAL: Available memory ${AVAILABLE}KB < ${THRESHOLD_KB}KB"
    exit 2
fi
echo "OK: Available memory ${AVAILABLE}KB"
exit 0

2. Configure systemd Service Memory Limits

Prevent a single runaway service from consuming all system memory:

# Example systemd service unit with memory protection
[Service]
MemoryMax=1G
MemoryHigh=800M
MemorySwapMax=500M
TasksMax=256

# MemoryHigh triggers throttling before MemoryMax hard limit
# MemorySwapMax limits swap usage specifically

3. Use cgroup-aware Application Design

Modern applications should detect their cgroup limits and adapt behavior:

// Example C snippet: read cgroup memory limit
#include 
#include 

long get_cgroup_memory_limit() {
    FILE *f = fopen("/sys/fs/cgroup/memory/memory.limit_in_bytes", "r");
    if (!f) return -1;
    long limit;
    fscanf(f, "%ld", &limit);
    fclose(f);
    return limit;
}

// Use this to size internal caches or decide to refuse requests

4. Enable Early OOM Daemons

Tools like earlyoom or nohang proactively kill processes before the system becomes completely unresponsive:

# Install earlyoom on Debian/Ubuntu
apt install earlyoom

# Configure thresholds in /etc/default/earlyoom
# Kill when available memory < 10% and swap < 10%
earlyoom -m 10 -s 10

# For systemd-based systems, enable the service
systemctl enable --now earlyoom

5. Perform Regular Memory Profiling in CI/CD

Catch memory regressions before they reach production:

# Example CI pipeline step: run app under memory limit, record RSS
#!/bin/bash
# Start app in background with a virtual memory limit
ulimit -v 1048576  # 1GB virtual memory max
./myapp &
PID=$!
sleep 30  # let it stabilize

# Sample RSS every second for 60 seconds
for i in $(seq 1 60); do
    RSS=$(grep VmRSS /proc/$PID/status | awk '{print $2}')
    echo "$(date +%s) $RSS" >> rss_log.txt
    sleep 1
done

kill $PID

# Fail CI if max RSS exceeds threshold (e.g., 500MB = 512000 KB)
MAX_RSS=$(awk '{print $2}' rss_log.txt | sort -n | tail -1)
if [ "$MAX_RSS" -gt 512000 ]; then
    echo "FAIL: Max RSS ${MAX_RSS}KB exceeds limit"
    exit 1
fi
echo "PASS: Max RSS ${MAX_RSS}KB within limit"

6. Understand and Tune Kernel Memory Reclaim

Kernel parameters that affect how aggressively memory is reclaimed:

# View current vm settings
sysctl -a | grep -E "vm.swappiness|vm.vfs_cache_pressure|vm.min_free_kbytes|vm.watermark"

# Common tuning (apply via sysctl)
vm.swappiness = 10          # reduce tendency to swap (default 60)
vm.vfs_cache_pressure = 50  # reclaim dentry/inode caches less aggressively (default 100)
vm.min_free_kbytes = 262144 # ensure 256MB always free for critical allocations
vm.watermark_scale_factor = 10 # reduce watermark scaling to reclaim sooner

# Apply persistently
echo "vm.swappiness=10" >> /etc/sysctl.d/99-memory-tuning.conf
echo "vm.vfs_cache_pressure=50" >> /etc/sysctl.d/99-memory-tuning.conf
sysctl --system

Real-World Troubleshooting Scenarios

Scenario A: "Cannot allocate memory" during SSH login

This often indicates kernel memory exhaustion from too many processes (fork bomb or zombie accumulation):

# Check process count
ps aux | wc -l
cat /proc/loadavg

# Check for zombies
ps aux | grep 'Z'

# If process count is absurdly high (thousands), identify the parent
pstree -p | head -50

# Temporarily raise kernel pid limit
echo 4194304 > /proc/sys/kernel/pid_max

# Kill the offending parent process tree
kill -9 -PARENT_PID   # negative PID = process group

Scenario B: malloc() fails in a container despite free host memory

The container's cgroup memory limit is hit. Verify and adjust:

# Inside container, check cgroup limit
cat /sys/fs/cgroup/memory/memory.limit_in_bytes
cat /sys/fs/cgroup/memory/memory.usage_in_bytes

# If limit is 1GB and usage is 1GB, allocation fails
# Solution: increase container limit (see Fix 7 above)
# Or reduce application memory usage

# Quick workaround: disable cgroup memory accounting (not recommended for production)
# This requires host access and is a temporary debug measure

Scenario C: Intermittent ENOMEM during high traffic spikes

This suggests memory pressure from transient allocations (network buffers, connection tracking):

# Check network-related memory
cat /proc/net/sockstat
# Look at TCP memory pressure
cat /proc/sys/net/ipv4/tcp_mem

# Increase network buffer limits
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216

# Increase connection tracking table size
sysctl -w net.netfilter.nf_conntrack_max=1048576

# For high-concurrency servers, pre-allocate hugepages for buffers
echo 1024 > /proc/sys/vm/nr_hugepages

Conclusion

The "Cannot allocate memory" error in Linux is a multifaceted issue that demands systematic diagnosis rather than blind restarts. By working through the diagnostic layers—system-wide memory, per-process limits, cgroup constraints, kernel overcommit policy, and application-level leaks—you can pinpoint the exact bottleneck. The fixes range from immediate operational interventions like dropping caches or increasing swap, to architectural changes such as configuring service memory limits, tuning kernel reclaim parameters, and implementing proactive monitoring. The best defense is a layered prevention strategy: set resource boundaries via cgroups and systemd, monitor memory pressure continuously, profile application memory in CI pipelines, and maintain sufficient swap as a safety net. With these practices in place, you transform ENOMEM from a mysterious outage trigger into a well-understood, manageable condition.

🚀 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