17
Arjun’s Systems Brief: I have spent years tuning memory-constrained systems in production. When a Linux desktop starts slowing down after hours of use, the first instinct is to blame the hardware. It is almost never the hardware. A process is leaking memory, and I will show you exactly how to find RAM leaks, prove they are leaking, and fix them without rebooting your entire system.
Why Your Linux Desktop Gets Slower Over Time
Every Linux user hits this moment. You boot up your desktop, everything feels snappy. Six hours later, switching between windows takes forever, your browser stutters, and htop shows 95% memory usage. The natural reaction is to think your RAM is insufficient. In most cases, that is wrong.
Linux aggressively uses free RAM for file caching. When you open a video file, the kernel loads it into memory. When you close the player, that data stays in RAM until something else needs it. This is by design. The kernel believes unused RAM is wasted RAM. So before you panic about high memory usage, you need to understand what “used” actually means.
There are two fundamentally different problems that look identical from the outside:
-
- High cache usage: The kernel is caching files efficiently. Your RAM is being utilized for performance. This is healthy.
- Memory leak: A process is allocating memory and never releasing it. Over hours or days, it consumes more and more RAM until the system thrashes. This is the problem I will teach you to diagnose.
The difference matters because the fix for high cache usage is “do nothing” while the fix for a memory leak is “find the culprit and kill it.” Mixing these up leads to terrible advice like automatically clearing your RAM cache every hour, which I covered in my guide to clearing RAM cache. That advice was wrong then and it is wrong now.
When you run free -h, look at the “available” column, not “free.” The available column shows memory that is genuinely available for new processes, including reclaimable cache. If available is above 20% of total, your system is not memory-starved.
Step 1: Spot the Suspect Process
The first step is identifying which process is consuming the most memory. I use three tools in combination because each one reveals something different.
top: Real-Time Memory Ranking
The top command sorted by memory gives you an instant snapshot of who is eating your RAM. The -o %MEM flag sorts by memory usage instead of CPU, which is exactly what you want for leak investigation.
fosslinux@ubuntu:~$ top -o %MEM | head -20
top - 23:41:22 up 3 days, 14:27, 0 users, load average: 0.23, 0.22, 0.09
Tasks: 301 total, 1 running, 300 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.0 us, 2.3 sy, 0.0 ni, 97.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 3350.6 total, 351.5 free, 1044.6 used, 2241.3 buff/cache
MiB Swap: 7958.0 total, 7923.1 free, 34.9 used. 2306.0 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2386 gdm-gre+ 20 0 4766636 188684 110028 S 0.0 5.5 1:04.57 gnome-s+
2941 gdm-gre+ 20 0 755456 95356 85280 S 0.0 2.8 0:00.76 mutter-+
2931 gdm-gre+ 20 0 241448 74764 63844 S 0.0 2.2 0:00.18 Xwayland
2990 gdm-gre+ 20 0 390384 69644 62904 S 0.0 2.0 0:00.30 ibus-x11
2965 gdm-gre+ 20 0 707376 44472 34572 S 0.0 1.3 0:02.05 xdg-des+
Look at the RES column, not VIRT. VIRT includes memory that is allocated but not actually resident in physical RAM. RES shows what is actually consuming your physical memory. In this capture, gnome-shell is using 188MB of real RAM, which is normal for a GNOME desktop session.
ps: Snapshot for Comparison
The ps command with sort-by-RSS gives you a cleaner snapshot that is easier to log over time:
fosslinux@ubuntu:~$ ps aux --sort=-rss | head -15 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND gdm-gre+ 2386 0.0 5.4 4766636 188684 ? Ssl Aug04 1:04 /usr/bin/gnome-shell --mode=gdm gdm-gre+ 2941 0.0 2.7 755456 95356 ? Sl Aug04 0:00 /usr/libexec/mutter-x11-frames gdm-gre+ 2931 0.0 2.1 241448 74764 ? S Aug04 0:00 /usr/bin/Xwayland :1024 -rootless gdm-gre+ 2990 0.0 2.0 390384 69644 ? Sl Aug04 0:00 /usr/libexec/ibus-x11 gdm-gre+ 2965 0.0 1.2 707376 44472 ? Ssl Aug04 0:02 /usr/libexec/xdg-desktop-portal-gnome root 3646 0.0 1.2 521540 42732 ? Ssl Aug04 0:11 /usr/libexec/fwupd/fwupd root 64367 0.0 0.8 2073284 28824 ? Ssl Aug05 0:16 /snap/snapd/current/usr/lib/snapd/snapd
I run this command every hour and save the output to a file. If a process’s RSS number keeps climbing across multiple snapshots, that is your leak suspect.
smem: The Accurate Memory View
The smem tool is the most accurate way to measure per-process memory because it uses PSS (Proportional Set Size) instead of RSS. The difference matters:
-
-
- RSS counts the full size of shared libraries for every process that uses them. If three processes share
libc.so, RSS counts it three times. - PSS divides shared library usage by the number of sharers. It gives you the “fair share” of memory that each process actually uses.
- RSS counts the full size of shared libraries for every process that uses them. If three processes share
-
fosslinux@ubuntu:~$ smem -t -p -s pss | head -20 PID User Command Swap USS PSS RSS 91676 fosslinux /bin/bash /tmp/vmware-fossl 0.00% 0.01% 0.03% 0.11% 91678 fosslinux /bin/bash /tmp/full_capture 0.00% 0.01% 0.04% 0.12% 91705 fosslinux head -20 0.00% 0.15% 0.16% 0.23% 91704 fosslinux /usr/bin/python3 /usr/bin/s 0.00% 0.37% 0.38% 0.47%
Insight: RSS is useful for a quick look, but PSS is what you want for accurate memory accounting. A process showing 500MB RSS might only be using 150MB of PSS if it shares most of its libraries with other processes. When tracking leaks, always use PSS over time.
Step 2: Track Memory Over Time (The Leak Test)
A single snapshot cannot prove a leak. You need to track memory usage over time and look for a pattern. A leak shows up as a steady, unbroken climb in RSS or PSS that never decreases. Normal memory usage fluctuates as the kernel caches and evicts data.
The Quick Manual Check
I start with a manual check. I grab the PID of the suspect process and poll it every minute:
fosslinux@ubuntu:~$ GS_PID=$(pgrep -o gnome-shell) fosslinux@ubuntu:~$ cat /proc/$GS_PID/status | grep -E 'VmRSS|VmSize|VmSwap' VmSize: 4766636 kB VmRSS: 188684 kB VmSwap: 8880 kB
Here, VmSize is the total virtual memory (including mappings that are not backed by physical pages), VmRSS is the actual physical memory in use, and VmSwap is memory that has been pushed to swap. If VmRSS grows by 5-10MB every hour without any user action, you have a leak.
The Automated Monitor Script
For a proper leak test, I use a simple bash script that logs memory usage to a CSV file. I let it run for 2-4 hours while doing normal work:
fosslinux@ubuntu:~$ cat monitor_mem.sh
#!/bin/bash
# Simple memory leak monitor script
PID=$1
INTERVAL=${2:-60}
LOGFILE="mem_log_${PID}.csv"
echo "timestamp,pid,rss_kb,vsz_kb" > $LOGFILE
echo "Monitoring PID $PID every ${INTERVAL}s. Press Ctrl+C to stop."
while kill -0 $PID 2>/dev/null; do
TIMESTAMP=$(date +%H:%M:%S)
...
sleep $INTERVAL
done
echo "Process $PID no longer running. Log saved to $LOGFILE"
Running it against gnome-shell:
fosslinux@ubuntu:~$ bash monitor_mem.sh 2386 2 23:41:26,2386,188684,4766636 23:41:26 | RSS: 188684KB | VSZ: 4766636KB 23:41:27,2386,188684,4766636 23:41:27 | RSS: 188684KB | VSZ: 4766636KB 23:41:28,2386,188684,4766636 23:41:28 | RSS: 188684KB | VSZ: 4766636KB
After a few hours, import the CSV into any spreadsheet tool and plot the RSS column over time. A leak produces a clear upward slope. Healthy memory usage produces a flat or oscillating line.
sar: Historical System-Wide Data
If you suspect a system-wide memory issue rather than a single process, sar from the sysstat package gives you historical data:
fosslinux@ubuntu:~$ sar -r 1 3 Linux 7.0.0-22-generic (ubuntu) 08/07/2026 _x86_64_ (4 CPU) 11:41:22 PM kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty 11:41:23 PM 354876 2356496 556064 16.21 67348 1986704 2859284 24.69 277624 2083576 188 11:41:24 PM 355952 2357676 554884 16.17 67348 1986792 2859284 24.69 277532 2083664 188 11:41:25 PM 355952 2357676 554876 16.17 67356 1986792 2859284 24.69 277532 2083664 216 Average: 355593 2357283 555275 16.18 67351 1986763 2859284 24.69 277563 2083635 197
The kbmemused column should stay relatively stable over time. If it climbs steadily day after day, something is leaking at the system level.
Why It Matters: I once spent three hours debugging a “memory leak” that turned out to be a web browser with 200 open tabs. Before you blame a kernel bug or a daemon, count your browser tabs and background applications. Chromium-based browsers are the number one cause of high memory usage on Linux desktops.
Step 3: Deep-Dive the Suspect Process
Once you have identified the suspect process, you need to understand what it is doing with all that memory. The pmap tool and /proc/PID/smaps give you a granular breakdown.
pmap: Memory Map Breakdown
The pmap -x command shows every memory mapping for a process, including which libraries are loaded, how much heap is allocated, and how much stack is in use:
fosslinux@ubuntu:~$ pmap -x 2386 | head -30 2386: /usr/bin/gnome-shell --mode=gdm Address Kbytes RSS Dirty Mode Mapping 0000000000400000 864 512 512 r-x-- gnome-shell 00000000006d8000 32 32 32 rw--- gnome-shell 00000000006e0000 3200 2176 2176 rw--- [ anon ] ...
The key columns are:
-
-
- Mapping: Which file or anonymous region this memory belongs to
- RSS: How much of this mapping is actually in physical RAM
- Dirty: How much has been modified (private to this process)
- Mode: r-x means read-execute (code), rw means read-write (data/heap)
-
If you see a large anonymous [anon] mapping that keeps growing in RSS across multiple pmap snapshots, that is likely where the leak lives.
/proc/PID/smaps: The Full Picture
The smaps file gives you the most detailed view of a process’s memory. It shows every mapping with 20+ metrics. The ones that matter for leak detection:
fosslinux@ubuntu:~$ sudo cat /proc/2386/smaps | head -50 c6c8e68000-c6c8e75000 r-xp 00000000 00:00 0 [anon:js-executable-memory] Size: 52 kB Rss: 52 kB Pss: 52 kB Private_Dirty: 52 kB Anonymous: 52 kB Swap: 0 kB
Private_Dirty is the number you want to watch. It represents memory that is both private to this process and has been modified. This is the memory that cannot be reclaimed without killing the process. If Private_Dirty keeps growing, that is your leak.
smem: Library-Level Breakdown
The smem -m command shows you which libraries and memory regions are consuming the most PSS:
fosslinux@ubuntu:~$ smem -m -P gnome-shell | head -20 Map PIDs AVGPSS PSS [heap] 1 3280 3280 /usr/lib/x86_64-linux-gnu/libc.so.6 1 215 215 /usr/lib/x86_64-linux-gnu/libm.so.6 1 195 195 /usr/lib/locale/locale-archive 1 82 82 [stack] 1 84 84 /usr/lib/python3.14/lib-dynload/_zstd.cp 1 60 60 /usr/lib/x86_64-linux-gnu/libzstd.so.1.5 1 58 58 /usr/lib/python3.14/lib-dynload/_lzma.cp 1 48 48 /usr/lib/x86_64-linux-gnu/ld-linux-x86-6 1 42 42 /usr/lib/python3.14/lib-dynload/_bz2.cpy 1 36 36 /usr/lib/x86_64-linux-gnu/libz.so.1.3.1 1 9 9
The [heap] entry is where malloc allocations land. If heap PSS keeps growing, the application is allocating memory and not freeing it. That is a textbook leak.
Step 4: Desktop-Specific Leak Culprits
After years of debugging Linux desktop memory issues, I can tell you that 90% of the leaks come from the same five sources. Here is my hit list, in order of frequency.
1. GNOME Shell Extensions
GNOME Shell extensions run inside the gnome-shell process. A buggy extension can leak memory that grows unbounded until your entire desktop becomes sluggish. I have seen extensions that leak 50MB per day of normal use.
Diagnosis: Disable all extensions, restart GNOME Shell, and monitor RSS for a few hours. If the leak disappears, re-enable extensions one by one until you find the culprit.
Fix: Run gnome-extensions list to see installed extensions. Disable them with gnome-extensions disable [extension-id]. Restart GNOME Shell with Alt+F2, type r, press Enter (X11 only; on Wayland, log out and back in).
2. Firefox Content Processes
Firefox uses separate content processes for tabs. Each process consumes memory, and some processes leak memory over time, especially with complex web applications like Google Docs or Slack web.
Diagnosis: Open about:memory in Firefox and click “Measure.” Look for content processes with unusually high memory compared to others.
Fix: Close unused tabs. In about:config, set dom.ipc.processCount to limit the number of content processes. Restart Firefox periodically if you are a heavy tab user.
3. Electron Apps (VS Code, Slack, Discord)
Electron apps are Chromium-based, which means they inherit Chromium’s memory characteristics. Each Electron app spawns multiple processes, and extensions or workspace configurations can cause leaks.
Diagnosis: Check memory usage of all processes belonging to the app:
fosslinux@ubuntu:~$ ps aux | grep -E 'code|slack|discord' | awk '{print $2, $6}'
Fix: Disable unnecessary extensions. For VS Code, open the Command Palette and run “Developer: Restart Extension Host.” For Slack, disable hardware acceleration in settings. Restart the app daily if memory usage concerns you.
4. PipeWire/WirePlumber
The PipeWire audio daemon can leak memory on certain hardware configurations, particularly with USB audio devices or Bluetooth headphones that connect and disconnect frequently.
Diagnosis: Monitor the pipewire process RSS over a few hours of audio playback.
Fix: Restart the audio stack: systemctl --user restart pipewire pipewire-pulse wireplumber
5. Xorg/Wayland Compositors
The display server itself can leak memory, especially with multiple monitors or high-resolution displays. This is less common in 2026 than it was a few years ago, but it still happens.
Diagnosis: Monitor the Xwayland or Xorg process RSS.
Fix: This usually requires a full desktop session restart (log out and log back in).
Worth Knowing: The OOM (Out of Memory) killer is Linux’s last line of defense. When memory gets critically low, the kernel kills the process with the highest oom_score. You can check a process’s score with cat /proc/PID/oom_score. If your leaky process keeps getting killed, the OOM killer is doing its job. The real fix is stopping the leak, not disabling the OOM killer.
Step 5: Fix the Leak
Finding the leak is only half the battle. Here is how to fix the most common causes.
Restart the Leaking Process
The simplest fix is to restart the leaking process. Use kill PID (SIGTERM) first, which gives the process a chance to clean up. Only use kill -9 PID (SIGKILL) if the process is unresponsive.
fosslinux@ubuntu:~$ kill 2386 # Graceful shutdown fosslinux@ubuntu:~$ kill -9 2386 # Force kill if unresponsive
For GNOME Shell, restarting kills your entire desktop session, so save your work first. On X11, you can restart just the Shell with Alt+F2 then r. On Wayland, you need to log out and log back in.
Disable Problematic Extensions
For GNOME Shell leaks caused by extensions:
fosslinux@ubuntu:~$ gnome-extensions list [email protected] fosslinux@ubuntu:~$ gnome-extensions disable [email protected]
Disable extensions one at a time, restart GNOME Shell, and monitor memory. This is the only reliable way to identify which extension is leaking.
Firefox Memory Optimization
Open about:memory in Firefox and click “Minimize memory usage.” This forces Firefox to release memory from inactive tabs. For persistent issues, set dom.ipc.processCount to 4 in about:config to limit content processes.
Electron App Fixes
For VS Code: Disable extensions you do not use. Open the Extensions view (Ctrl+Shift+X) and disable or uninstall anything you have not used in the past week. Also disable GPU acceleration if you notice memory growth: add "disable-hardware-acceleration": true to your settings.
For Slack and Discord: Disable hardware acceleration in Settings > Advanced. This prevents GPU memory leaks that are common with Electron apps.
Restart Audio Services
If PipeWire is leaking:
fosslinux@ubuntu:~$ systemctl --user restart pipewire pipewire-pulse wireplumber
This restarts the entire audio stack without affecting your desktop session. Audio will briefly cut out and resume automatically.
Step 6: Prevent Future Leaks
Fixing leaks after they happen is reactive. Here is how to prevent them from causing problems in the first place.
systemd Resource Limits
You can cap memory usage per application using systemd’s MemoryMax directive. This does not fix the leak, but it prevents a single leaky process from killing your entire system:
fosslinux@ubuntu:~$ systemctl --user edit firefox.service [Service] MemoryMax=4G MemoryHigh=3G
When Firefox exceeds 3GB, the kernel starts reclaiming memory aggressively. At 4GB, it gets OOM-killed. This protects your system while you investigate the root cause.
Pro Tip: For desktop applications, MemoryMax is more useful than MemoryHigh. MemoryHigh triggers kernel reclaim, which can make the application sluggish. MemoryMax triggers OOM kill, which is a clean restart. For a browser, a clean restart is better than a sluggish 10 minutes.
Automated Monitoring
Set up a simple cron job that monitors your most leak-prone processes and alerts you when memory usage crosses a threshold:
fosslinux@ubuntu:~$ crontab -e
# Check Firefox memory every 30 minutes, alert if RSS > 3GB
*/30 * * * * RSS=$(ps -C firefox -o rss= | awk '{sum+=$1} END {print sum}'); if [ $RSS -gt 3145728 ]; then notify-send "Firefox memory alert" "RSS: $((RSS/1024))MB exceeds 3GB"; fi
Keep Your System Updated
Memory leaks are bugs. Bug fixes come with updates. Keep your desktop environment, browser, and applications updated. Most distribution update managers will notify you of available updates.
Deep Dive: Using Valgrind for Application Developers
If you are developing applications and need to find the exact source of a memory leak, Valgrind’s Memcheck tool is the gold standard. It instruments your program at runtime and reports every allocation that is not freed.
fosslinux@ubuntu:~$ gcc -g -o leak_test leak_test.c fosslinux@ubuntu:~$ valgrind --leak-check=yes ./leak_test ==91727== Memcheck, a memory error detector ==91727== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al. ==91727== Using Valgrind-3.26.0 and LibVEX; rerun with -h for copyright info ==91727== Command: /tmp/leak_test Allocated 100 ints at 0x4aa0040 ==91727== HEAP SUMMARY: ==91727== in use at exit: 400 bytes in 1 blocks ==91727== total heap usage: 2 allocs, 1 frees, 4,496 bytes allocated ==91727== 400 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==91727== at 0x4850858: malloc (vg_replace_malloc.c:447) ==91727== LEAK SUMMARY: ==91727== definitely lost: 400 bytes in 1 blocks ==91727== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
The output tells you exactly where the leak is: leaky_function at line 5 of leak_test.c called malloc but never freed it. Compile with -g to get line numbers in the report.
Valgrind vs. heaptrack: Valgrind runs 20-30x slower and uses significantly more memory. Use it for final verification, not during normal development. For profiling memory allocations in production applications, heaptrack (from KDE) has much lower overhead and can attach to running processes.
FAQ
Is high RSS always a memory leak?
No. RSS includes shared libraries, which can be shared across processes. A process with 500MB RSS might only be using 150MB of PSS. High RSS is a starting point for investigation, not proof of a leak. You need to track RSS over time to confirm a leak pattern.
How do I know if it is a leak or just caching?
Memory leaks show a steady, unbounded increase in RSS or Private_Dirty over hours or days. Caching shows fluctuation: memory usage goes up when you open files and down when the kernel reclaims cache. If your memory usage stabilizes after an initial spike, it is caching. If it never stops growing, it is a leak.
Should I clear my RAM cache regularly?
No. This is one of the most persistent pieces of bad advice on the internet. Clearing your page cache forces the kernel to re-read everything from disk, making your system slower, not faster. I explained why in my guide to clearing RAM cache. Only clear cache if you are benchmarking disk performance or debugging a specific issue.
What about zswap and zram?
These are compressed memory subsystems that help when you are running low on RAM. They compress pages in memory before swapping to disk. They do not fix memory leaks, but they can extend the usable life of memory-constrained systems. I covered them in detail in my RAM optimization guide.
Can I use GNOME System Monitor to track leaks?
Yes, but it is not ideal. GNOME System Monitor shows memory usage in real-time, but it does not log history or export data. For leak detection, you need to track memory over time and look for trends. The ps command with a logging script or sar gives you the historical data you need.
What if the leak is in a kernel module?
Kernel memory leaks are rare on desktop systems but do happen. Symptoms include /proc/meminfo showing high “Slab” or “KernelStack” values that never decrease. Debugging kernel leaks requires tools like slabtop and /proc/slabinfo. This is advanced territory; if you suspect a kernel leak, file a bug report with your distribution.
Conclusion
Memory leaks on Linux desktops are common but diagnosable. The workflow is straightforward: identify the suspect process with top or smem, track its memory over time with a monitoring script, deep-dive its memory map with pmap or smaps, and then fix the culprit based on what you find.
The most important lesson I have learned after years of debugging memory issues is this: do not assume high memory usage is a problem. Linux uses RAM aggressively for caching, and that is a feature, not a bug. The real problem is when memory usage grows without bound and never decreases. That is a leak, and now you have the tools to find it.
For more memory management techniques, see my guides on clearing RAM cache, zswap and zram optimization, and systemd resource limits.