Memory overcommit means assigning more RAM to virtual machines than the host physically has. It works because guests rarely use all of their memory at the same time, and because identical pages (the same kernel, the same libraries) can be stored once. Done carelessly, it ends with the host swapping heavily or the kernel's OOM killer terminating a guest. In this tutorial you will measure your current allocation, enable the three mechanisms KVM offers (virtio ballooning, Kernel Samepage Merging and compressed swap), and set up the signals that tell you when you have gone too far, on an Ubuntu 24.04 host with libvirt.
Prerequisites
To follow this guide you need:
- A physical or bare metal server running Ubuntu 24.04 LTS with KVM and libvirt installed (
qemu-kvm,libvirt-daemon-system,libvirt-clients). - A non-root user with
sudoprivileges. - One or more running Linux guests. This guide uses one called
vm1with 4 GiB of RAM.
Step 1 - Measuring host memory and current allocation
Start with what the host has:
free -h
total used free shared buff/cache available
Mem: 62Gi 41Gi 1.2Gi 12Mi 20Gi 20Gi
Swap: 8.0Gi 0.0Ki 8.0Gi
The available column is the real headroom: free memory plus cache the kernel can reclaim. Now add up the maximum memory assigned to all running guests. virsh dominfo reports it in KiB:
for vm in $(sudo virsh list --name); do
sudo virsh dominfo "$vm" | awk -v vm="$vm" '/^Max memory/ {printf "%-20s %6.1f GiB\n", vm, $3/1048576}'
done
vm1 4.0 GiB
web01 16.0 GiB
db01 32.0 GiB
build01 16.0 GiB
In this example, 68 GiB is assigned on a 62 GiB host, an overcommit ratio of about 1.1:1. Keep this number in mind; the rest of the guide is about raising it without hurting guests.
| Ratio | Typical use |
|---|---|
| Up to 1.2:1 | Production guests, databases. Ballooning and KSM absorb the difference. |
| 1.2:1 to 1.5:1 | Mixed workloads with many similar guests. Requires monitoring from Step 5. |
| Above 1.5:1 | Test, CI and desktop environments where occasional slowdowns are acceptable. |
Step 2 - Using the virtio balloon to reclaim guest memory
A balloon driver inside the guest allocates memory on request from the host and hands it back, so the host can reclaim RAM from an idle guest without stopping it. Guests created with virt-install include a balloon device by default. Check:
sudo virsh dumpxml vm1 | grep -A2 memballoon
<memballoon model='virtio'>
<address type='pci' domain='0x0000' bus='0x05' slot='0x00' function='0x0'/>
</memballoon>
If it shows model='none' or no block at all, add the device with sudo virsh edit vm1, placing it inside <devices>:
<memballoon model='virtio'>
<stats period='10'/>
</memballoon>
<stats period='10'/> makes the guest report its memory usage every 10 seconds. The change takes effect after a full shutdown and start. Linux guests load the virtio_balloon driver automatically.
To enable statistics on a running guest without restarting:
sudo virsh dommemstat vm1 --period 10 --live
Then read them:
sudo virsh dommemstat vm1
actual 4194304
swap_in 0
swap_out 0
major_fault 812
minor_fault 2211904
unused 2764112
available 4020308
usable 3102460
last_update 1758792345
rss 1603820
actual is the memory currently given to the guest, unused is what the guest is not using at all, and rss is what the guest really occupies on the host. Here the guest has 4 GiB but only needs about 1.5 GiB.
Shrink the guest's memory while it runs. The value must be at or below the maximum memory:
sudo virsh setmem vm1 2G --live
Check the result from inside the guest with free -h: the total drops to about 2 GiB. To give the memory back, run sudo virsh setmem vm1 4G --live.
To let a guest grow above its boot size later, its maximum (<memory>) must be larger than its current size (<currentMemory>). Set the maximum while the guest is off:
sudo virsh shutdown vm1
sudo virsh setmaxmem vm1 8G --config
sudo virsh setmem vm1 4G --config
sudo virsh start vm1
The guest boots with 4 GiB and can be ballooned up to 8 GiB.
NoteBallooning is manual in libvirt: nothing resizes guests for you. Deflate idle guests during quiet periods, and never balloon a guest below what its workload needs, or it will start swapping inside.
Step 3 - Enabling Kernel Samepage Merging
KSM scans the memory of processes that opt in (QEMU does so for all guest RAM), finds identical pages, and merges them into a single copy-on-write page. Ten Ubuntu guests running the same kernel share a large amount of memory.
Check whether KSM is running:
cat /sys/kernel/mm/ksm/run
0 means stopped. Start it:
echo 1 | sudo tee /sys/kernel/mm/ksm/run
The scanner rate is controlled by two values. The defaults are conservative; raising pages_to_scan merges faster at the cost of some host CPU:
echo 1000 | sudo tee /sys/kernel/mm/ksm/pages_to_scan
echo 20 | sudo tee /sys/kernel/mm/ksm/sleep_millisecs
These settings are lost at reboot. Make them persistent with a tmpfiles.d entry, which systemd applies at boot:
sudo nano /etc/tmpfiles.d/ksm.conf
w /sys/kernel/mm/ksm/pages_to_scan - - - - 1000
w /sys/kernel/mm/ksm/sleep_millisecs - - - - 20
w /sys/kernel/mm/ksm/run - - - - 1
Apply it now to confirm the file is valid:
sudo systemd-tmpfiles --create /etc/tmpfiles.d/ksm.conf
Give KSM a few minutes, then check how much it saves:
grep -H . /sys/kernel/mm/ksm/pages_shared /sys/kernel/mm/ksm/pages_sharing /sys/kernel/mm/ksm/general_profit
/sys/kernel/mm/ksm/pages_shared:182340
/sys/kernel/mm/ksm/pages_sharing:1204518
/sys/kernel/mm/ksm/general_profit:5467201536
pages_sharing counts the pages being deduplicated and general_profit is the net saving in bytes (about 5.1 GiB here) after subtracting KSM's own metadata. If pages_sharing stays low after an hour, your guests are too different for KSM to help and you can turn it off to save CPU.
WarningPage sharing between guests has been used for side-channel attacks between VMs. Do not enable KSM on hosts that run guests from mutually untrusted tenants. To exclude a single sensitive guest, add
<nosharepages/>inside its<memoryBacking>block.
Step 4 - Adding compressed swap with zswap
When overcommit goes wrong, the host must swap. zswap reduces the cost by compressing pages into a RAM pool before anything reaches the disk. It needs a swap device behind it. Check your swap:
swapon --show
If nothing is listed, create an 8 GiB swap file:
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Check whether zswap is enabled on the running kernel:
cat /sys/module/zswap/parameters/enabled
If it prints N, enable it now and pick a compressor and pool limit:
echo zstd | sudo tee /sys/module/zswap/parameters/compressor
echo 20 | sudo tee /sys/module/zswap/parameters/max_pool_percent
echo 1 | sudo tee /sys/module/zswap/parameters/enabled
max_pool_percent caps the compressed pool at 20 % of RAM. To make it permanent, add the same settings to the kernel command line:
sudo nano /etc/default/grub
Append the options to the existing GRUB_CMDLINE_LINUX_DEFAULT value, keeping anything already there:
GRUB_CMDLINE_LINUX_DEFAULT="zswap.enabled=1 zswap.compressor=zstd zswap.max_pool_percent=20"
sudo update-grub
Also lower the host's swappiness so the kernel prefers dropping cache over swapping guest memory:
echo 'vm.swappiness = 10' | sudo tee /etc/sysctl.d/60-swappiness.conf
sudo sysctl --system
Step 5 - Watching for memory pressure
Free memory alone does not tell you whether guests are suffering. The kernel's Pressure Stall Information shows the share of time tasks were stalled waiting for memory:
cat /proc/pressure/memory
some avg10=0.00 avg60=0.00 avg300=0.00 total=184224
full avg10=0.00 avg60=0.00 avg300=0.00 total=90312
some is the percentage of time at least one task was stalled, full the percentage when all were. Values near zero are healthy. Sustained some avg60 above 10 or any sustained full value means your overcommit is hurting performance.
Check swap activity with vmstat, which prints a line every 5 seconds:
vmstat 5
The si and so columns are pages swapped in and out per second. Occasional swap out is acceptable; constant swap in means guests are waiting on disk.
For each guest, swap_in and major_fault in virsh dommemstat show whether the guest itself is short of memory after ballooning.
Finally, check whether the OOM killer has already acted:
sudo journalctl -k --since "7 days ago" | grep -i "out of memory"
A QEMU process in that output means a guest was killed. Reduce the overcommit ratio or add RAM before relying on that host again.
Troubleshooting
virsh setmem returns error: invalid argument: cannot set memory higher than max memory. Raise the maximum with setmaxmem ... --config while the guest is off (Step 2).
setmem succeeds but the guest's memory does not change. The balloon driver is not loaded in the guest. Check with lsmod | grep virtio_balloon inside the guest, and confirm the <memballoon> device exists.
KSM uses a lot of CPU. The ksmd kernel thread shows in top. Reduce pages_to_scan or increase sleep_millisecs.
Guests are killed by the OOM killer even with swap. The host ran out of memory and swap together. Lower the overcommit ratio, balloon idle guests down, or protect critical guests by keeping their memory fully backed with huge pages, which are never reclaimed.
Conclusion
You measured your overcommit ratio, learned to reclaim memory from idle guests with the virtio balloon, deduplicated identical pages with KSM, added a compressed swap tier with zswap, and set up the pressure metrics that tell you when to stop. Next, consider exporting /proc/pressure/memory and virsh dommemstat to your monitoring system, pinning critical guests to dedicated CPUs and NUMA nodes, and reviewing the ratio each time you add guests.
