Linux cgroup Memory Limits: Controlling Application Memory Usage
How to set and manage memory limits for processes using Linux cgroups, including practical examples for Java, Node.js, and containerized workloads.
Introduction
Without memory limits, a single application can consume all available RAM and starve the entire system. Linux cgroups (control groups) provide a kernel-level mechanism to allocate, restrict, and account resource usage per process or group of processes. Understanding cgroups is essential for running multi-tenant systems, containers, and any environment where memory isolation matters.
Environment
The examples use cgroup v2 on Debian 12, which is the modern standard. For systems still running cgroup v1, the concepts are similar but the interface differs.
stat -f -c %T /sys/fs/cgroup/
# cgroup2fs
cat /proc/filesystems | grep cgroup
# nodev cgroup
# nodev cgroup2Problem
A development server runs multiple services on a single machine. One service with a memory leak gradually consumes all available RAM, causing the OOM killer to terminate other services or the entire system becoming unresponsive.
# Without limits
ps aux --sort=-%mem | head -5
# USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# appuser 1234 5.2 89.3 12345678 7340234 ? Sl 10:00 45:23 java -Xmx6g -jar app.jarThe Java process is using 89% of system memory with no way for other processes to compete.
Analysis
cgroups work by assigning processes to groups and applying resource limits to those groups. For memory, cgroups can:
- Set hard limits (
memory.max) that trigger OOM when exceeded - Set soft limits (
memory.high) that throttle before hitting the hard limit - Track memory usage (
memory.current) - Account for cache vs anonymous memory
- Control swap usage (
memory.swap.max)
The cgroup filesystem is typically at /sys/fs/cgroup/:
ls /sys/fs/cgroup/
# cgroup.controllers cgroup.stat init.scope system.slice user.sliceEach directory represents a cgroup. Processes are assigned by writing their PID to cgroup.procs.
Solution
1. Create a cgroup with memory limits
sudo mkdir /sys/fs/cgroup/java-app
echo "5G" | sudo tee /sys/fs/cgroup/java-app/memory.max
echo "4G" | sudo tee /sys/fs/cgroup/java-app/memory.high
echo $JAVA_PID | sudo tee /sys/fs/cgroup/java-app/cgroup.procsmemory.max is the hard limit. When exceeded, the OOM killer activates within the cgroup. memory.high is a soft limit that triggers memory reclaim and throttling before the hard limit is reached.
2. Check memory usage of a cgroup
cat /sys/fs/cgroup/java-app/memory.current
# 4831838208 # ~4.5 GB in bytes
cat /sys/fs/cgroup/java-app/memory.stat
# anon 4294967296
# file 536870912
# slab 123456783. Use systemd to manage cgroup limits
# /etc/systemd/system/myapp.service
[Service]
MemoryMax=5G
MemoryHigh=4G
ExecStart=/usr/bin/java -jar /opt/myapp/app.jarApply immediately:
sudo systemctl daemon-reload
sudo systemctl restart myapp.serviceVerify:
systemctl show myapp.service | grep -i memory
# MemoryMax=5368709120
# MemoryHigh=4294967296
# MemoryCurrent=42949672964. Set limits for a process tree
For a shell and all its children:
sudo mkdir /sys/fs/cgroup/build-env
echo "2G" | sudo tee /sys/fs/cgroup/build-env/memory.max
echo $$ | sudo tee /sys/fs/cgroup/build-env/cgroup.procs
make -j4 # This process and children are now limited5. Monitor cgroup memory usage
# Real-time monitoring
watch -n 1 cat /sys/fs/cgroup/java-app/memory.current
# Or use systemd-cgtop
sudo systemd-cgtop
# Path Tasks %CPU Memory Input/s Output/s
# /system.slice/java-app.service 12 5.2 4.5G - -6. Handle OOM within a cgroup
When a cgroup hits its memory limit:
# Check OOM events
cat /sys/fs/cgroup/java-app/memory.events
# oom 1
# oom_kill 1
# The OOM killer within the cgroup kills only processes in that cgroup
# Other cgroups are unaffected7. Set swap limits
echo "1G" | sudo tee /sys/fs/cgroup/java-app/memory.swap.max8. Use cgroup namespaces for isolation
sudo unshare --cgroup --mount-proc bash
# Now in a new cgroup namespace
echo "1G" > /sys/fs/cgroup/memory.max9. Reset limits when done
echo "max" | sudo tee /sys/fs/cgroup/java-app/memory.max
echo "max" | sudo tee /sys/fs/cgroup/java-app/memory.high10. Remove a cgroup
# First, move all processes out
for pid in $(cat /sys/fs/cgroup/java-app/cgroup.procs); do
echo $pid | sudo tee /sys/fs/cgroup/cgroup.procs
done
sudo rmdir /sys/fs/cgroup/java-appLessons Learned
cgroups are the foundation of container resource management. Setting MemoryHigh (soft limit) before MemoryMax (hard limit) gives applications time to clean up before being killed. For Java applications, ensure the heap plus native memory stays below the cgroup limit. Monitor cgroup memory with systemd-cgtop to catch applications approaching their limits before they are terminated. And always test cgroup limits in staging before applying them to production services.
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.