Task Manager says "VmmemWSL" is holding 8 GB, while inside Ubuntu, "htop" shows something closer to 2 GB. At first, the difference is difficult to interpret because both numbers look perfectly plausible when considered on their own.
You close a few browser tabs, stop a container, run "wsl --shutdown", and perhaps even try a ".wslconfig" setting you found in a forum post. Yet, somehow, the numbers still don't seem to line up.
For a while, I assumed there was probably something wrong with my machine. Maybe a memory leak, maybe a bug, or simply one of those strange behaviors that you eventually learn to associate with WSL without really understanding what is happening underneath.
Then I started looking more carefully at what each number actually meant.
That changed the question for me. The problem wasn't necessarily that one of these tools was wrong; I was comparing numbers that didn't have the same scope in the first place.
Once I understood that, another tempting idea turned out to be wrong as well: adding up the memory reported by every Linux process does not give you the memory used by WSL2.
This is where Wisely started for me. I wasn't initially thinking about building a monitoring tool. I was trying to answer a much less glamorous question:
"What does this number actually mean?"
Three scopes, three different numbers
The first distinction that really helped me make sense of WSL2 was the distinction between scope. A memory figure can describe the Windows host, the WSL2 virtual machine, one Linux distribution, or a single process running inside that distribution, and those are four different things even though they all eventually relate to the same physical RAM.
Windows host [host]
└─ WSL2 virtual machine ("VmmemWSL") [vm]
├─ Distro A [distro]
│ ├─ python3 [process]
│ └─ node [process]
└─ Distro B [distro]

Figure 1 — The four scopes of WSL2 memory: host, VM, distro, process. (Illustrative example — the process names are not from the article.)
At the host level, Windows is looking at the machine as a whole. At the VM level, "VmmemWSL" represents the WSL2 virtual machine, including the memory footprint of the VM itself, the Linux kernel, caches, and workloads from the distributions running inside it.
At the distro level, tools such as "/proc/meminfo" describe what a particular Linux environment sees, while at the process level, "ps", "/proc//...", "top", or "htop" describe individual processes and their memory mappings.
The important part isn't simply that these values are different; it is that they answer different questions.
That means comparing an 8 GB VM-level figure with a 2 GB distro-level figure and expecting them to match is really a category error. The two measurements aren't necessarily contradicting each other because they aren't describing the same thing.
I actually built this mistake into a tool of my own. At one point, I had an alert comparing a VM-level figure against a threshold defined at distro level. Both numbers looked perfectly reasonable on the screen, but the alert was still mathematically incapable of firing correctly.
The problem wasn't the threshold. It was the comparison itself.
That was one of the first moments where I started thinking about monitoring differently: a metric needs a meaning before it can be useful.
The trap that looks like the obvious solution: summing RSS
Once you suspect that "VmmemWSL" is showing "too much" memory, the most intuitive next step is to go inside Linux and look at the processes. For example:
ps -eo rss,comm --sort=-rss
Then you can sum the RSS values, and at first this feels like a reasonable approximation of the total. After all, if every process reports how much memory it has resident, adding those values together should seem like it ought to tell you how much memory the system is using.
I initially thought the same thing, until I looked more closely at what RSS actually represents.
RSS is not a unique physical-memory ownership number
RSS — Resident Set Size — tells you how much memory is resident for a process. The important detail is that it does not mean that every byte counted by that process is uniquely owned by that process.
A process can map pages that are also mapped by other processes. Shared libraries are an obvious example, but shared memory is another. "fork()" creates another important case: parent and child processes can initially point to the same physical pages until one of them writes to those pages and copy-on-write creates a private copy.
As a result, the same physical page can contribute to the RSS reported for more than one process.
A simplified picture looks like this:
Reported (RSS): What's physically there:
process A RSS = 30 MB shared page(s) = 24 MB (exists ONCE)
process B RSS = 30 MB + A's private pages = 6 MB
process C RSS = 30 MB + B's private pages = 6 MB
------------------------ + C's private pages = 6 MB
sum(RSS) = 90 MB ------------------------------
real total = 42 MB
90 MB reported vs 42 MB real -- the 24 MB shared page got counted 3 times.

Figure 2 — Why summing RSS overcounts: a shared page gets attributed to every process that maps it.
Suppose three processes all map the same physical page. That page exists only once in physical memory, but it can appear in the RSS reported for all three processes.
Now scale that up to hundreds or thousands of processes, shared libraries, memory mappings, and other forms of sharing. If I simply add the RSS column, I'm no longer calculating unique physical memory; I'm calculating the sum of per-process resident mappings.
Those are not the same thing.
This is why the total can even become larger than the memory footprint of the VM. If I then calculate something like:
VmmemWSL − sum(RSS)
I can get a negative number.
At first glance, that looks broken. In reality, the subtraction is doing exactly what I asked it to do. The problem is that one of the quantities being subtracted is an over-counted attribution measure.
Linux itself documents that some of the values exposed through "/proc//statm" are not precise and points to "smaps" or "smaps_rollup" when accurate detailed accounting is required. Even then, using those mechanisms doesn't magically turn per-process accounting into a single authoritative VM total.
That distinction matters because per-process memory numbers are still useful. They can help answer a question such as:
"Which processes appear to be associated with a large part of the resident memory?"
What they should not pretend to answer is:
"How much RAM does this process uniquely consume?"
Those are different questions, and pretending that they are the same is exactly how a monitoring tool can turn a technically valid measurement into a misleading conclusion.
So the monitoring rule I ended up with is deliberately conservative:
process memory is attribution, not a total.
If a tool shows an attribution breakdown, the part that cannot be cleanly attributed should remain visible instead of being hidden simply to make the numbers add up.
Then there is the Linux side of the story
There is another source of confusion once you start looking at memory from inside Linux. People often run something like:
cat /proc/meminfo
and immediately focus on "MemFree", which is understandable because the name seems to answer the question directly.
The problem is that it doesn't necessarily tell you what you actually want to know.
"MemFree" represents memory that is currently unused. Linux, however, is generally much more willing to use spare RAM for useful things such as the filesystem page cache than to leave it sitting completely idle.
So a low "MemFree" value is not, by itself, evidence of a memory problem.
"MemAvailable" is usually more useful when the question is:
"How much memory can this system make available without immediately running into memory pressure?"
The kernel documentation describes "MemAvailable" as an estimate of how much memory is available for starting new applications, taking reclaimable kernel memory into account.
That leads to a distinction that became increasingly important to me:
used ≠ needed
free ≠ available
The same physical RAM can therefore produce several different readings depending on what you're asking the system to describe:
actual: [---- used ----][--- cache ----][ free ]
MemFree: [ free ]
MemAvail.: [--- cache ----][ free ]

Figure 3 — Schematic only, no measured values: MemFree only sees the free slice; MemAvailable also counts reclaimable cache.
A machine can have very little "MemFree" while still having a healthy amount of reclaimable memory. In other words, two numbers can both be completely correct and still lead you toward very different conclusions if you don't first understand what they measure.
The VM adds another layer
WSL2 makes the situation more interesting because Linux is not the whole picture. There is a virtual machine underneath it, and Windows is observing that VM from outside the Linux environment.
From Windows, Task Manager can show the memory footprint of that VM through "VmmemWSL" on current systems. Microsoft has also documented the older "Vmmem" naming in its WSL memory-reclaim material.
That number is useful, but the important question is again what it actually represents.
It isn't really answering:
"How much memory are my applications logically using?"
It is closer to:
"What is the current memory footprint of the WSL2 VM as seen by Windows?"
That footprint can include memory used by the Linux kernel and by caches in addition to application memory. Microsoft has explicitly demonstrated a case where Linux showed very little memory in active use while the VM still held a much larger footprint because of page cache.
This was one of the parts that initially felt strange to me. Windows is looking at the VM from the outside, Linux is looking at its own memory model from inside the VM, and a process is looking at its own mappings. None of those views is "the one true memory number"; each one is answering a different question.
Cache is memory, but it is not the same kind of memory
This is where I think the wording matters more than it might initially seem.
Seeing 8 GB in "VmmemWSL" does not automatically mean that applications currently require 8 GB of RAM. Linux uses memory for caching because keeping frequently accessed data in RAM can make the system faster, and that memory can later be reclaimed when it is needed somewhere else.
Microsoft's documentation on WSL2 memory reclamation describes exactly this behavior: memory can be returned to Windows when it is no longer needed in the Linux guest, and cached memory is part of that picture.
So I started separating three ideas that I had previously treated as if they were the same:
- memory currently in the VM
- memory actively used by workloads
- memory that could be reclaimed
These values are related, but they are not interchangeable.
The phrase "memory actually needed" is particularly dangerous because it sounds like an objective quantity that can simply be read from a counter. In reality, it is an estimate based on assumptions about what can be reclaimed and what cannot.
That distinction matters because the word "needed" sounds much more precise than the underlying measurement often is.
".wslconfig" changes the context too
There is also the configuration layer. For example:
[wsl2]
memory=8GB
processors=8
The "memory" setting defines how much memory can be assigned to the WSL2 virtual machine. Microsoft currently documents the default as 50% of the total Windows memory. "processors" controls how many logical processors are assigned to the VM, with the default being the Windows logical-processor count.
That does not mean:
memory=8GB
is equivalent to:
WSL is currently using 8GB
The first describes a configured policy or ceiling, while the second describes an observed usage value. They belong to different categories of information, even though both are expressed using the same unit.
configured ceiling (memory=8GB): ------------------------- <- the most it's ALLOWED to take
actual usage, sampled over time: ▂▃▅▇▆▄▃▂▃▅▆▇▅▃▂▁▂▃▅▆▇▆▄▃▂ <- moves freely underneath, on its own

Figure 4 — Schematic only, no measured values: a configured ceiling is a policy; actual usage is an independent observation.
This sounds obvious when written down, but it is surprisingly easy to forget once those values are placed next to each other on a dashboard.
And then I looked at "autoMemoryReclaim"
This is another example of why a monitoring tool needs to know not only what it measured, but also under which configuration and version it measured it.
WSL supports:
[experimental]
autoMemoryReclaim=dropCache
with three documented modes:
- "disabled"
- "gradual"
- "dropCache"
The current Microsoft documentation lists "dropCache" as the default. In "gradual" mode, cached memory is reclaimed progressively; in "dropCache", cached memory is reclaimed immediately.
This is worth emphasizing because defaults have changed over time.
Microsoft originally introduced "autoMemoryReclaim" as an opt-in experimental feature with "disabled" as the default. Later WSL releases changed the default to "dropCache".
That means an article, script, or forum post written for an older WSL release can point you toward a perfectly reasonable configuration that is no longer the current default.
This is another thing Wisely needs to make explicit: configuration is part of the meaning of a measurement.
Two systems can run broadly similar workloads and still produce different memory-footprint curves because their reclaim behavior differs.
CPU has the same problem
Once I started looking for this pattern elsewhere, I found it again with CPU.
Take:
nproc
It tells you how many processors the Linux guest sees, but it doesn't tell you how much CPU is currently being used.
Now take:
cat /proc/loadavg
The first three values are load averages over 1, 5, and 15 minutes. However, load average isn't a CPU percentage either.
Linux defines it in terms of runnable tasks and tasks waiting in uninterruptible I/O states. Consequently:
loadavg = 4.0
doesn't mean:
CPU = 400%
and it certainly doesn't mean:
CPU = 50% on an 8-core machine
A high load can reflect CPU contention, but it can also reflect tasks stuck waiting on I/O.
Once again, the number itself is real; the problem appears when we give it a meaning that the measurement does not actually support.
At this point, the problem started looking different
Originally, I thought I was trying to answer a fairly simple question:
"Why is WSL2 using so much RAM?"
The more I investigated, however, the more I realized that the question itself was underspecified.
What exactly is "WSL2" in this context? Are we talking about the Windows host, the VM, one distro, or a particular process?
And what exactly does "using" mean? Are we talking about resident memory, memory available to the guest, page cache, memory that could be reclaimed, or simply a configured ceiling?
Once I started asking those questions, the strange numbers became much easier to explain.
The difficult part wasn't collecting more metrics. It was assigning the right meaning to the metrics I already had.
That's where Wisely came from
Wisely is a small read-only PowerShell tool I built around this problem.
I didn't build it because I think I have discovered a magical new source of memory information. I haven't. Most of the underlying information already exists in the operating system and in the tools we already use.
The idea is to make the differences between those measurements much harder to overlook.
For each value, Wisely should be able to tell me things such as:
- what scope it belongs to;
- where it came from;
- how fresh it is;
- whether it is direct, attributed, estimated, or merely correlated;
- what I am actually allowed to conclude from it.
That last part matters more than it might sound.
A monitoring system can easily produce a number that is technically correct while still leading its user toward a false conclusion. That is why I started treating the semantic contract of a metric almost like an API contract: if an API response has a type, a unit, and a defined meaning, a monitoring metric should have the same kind of discipline.
A simplified representation looks like this:
{
"timestamp": "2026-08-27T14:03:11Z",
"scope": "distro",
"entity": "Ubuntu",
"metric": "memory.available",
"value": 3120,
"unit": "MB",
"source": "/proc/meminfo",
"confidence": "high",
"freshnessMs": 340,
"attribution": "direct"
}
The exact serialization isn't the important part. What matters is that a measurement should not lose the context that gives it meaning while moving through the monitoring pipeline.
What Wisely deliberately refuses to do
This is probably the part of the project I care about most because it is easy to build a tool that looks useful simply by displaying more numbers.
Sometimes the safest answer is:
"I don't have enough information to calculate that honestly."
For example, Wisely should not turn the sum of process RSS values into a fake "total process memory". It should not turn "loadavg" into a CPU percentage, compare a VM-level number against a distro-level threshold simply because both are expressed in gigabytes, or present an estimate as if it were a direct observation.
It also should not silently hide the remainder when a process-level attribution cannot account for the whole VM footprint.
This leads to a simple classification:
| Type | Meaning |
|---|---|
| Direct | Read directly from its source |
| Attributed | Associated with an entity according to an explicit rule |
| Estimated | Derived from measurements and assumptions |
| Correlated | Observed alongside another change without proving causation |
The wording matters.
Saying:
"'python3' has 1.2 GB RSS"
is a measurement.
Saying:
"'python3' consumes 1.2 GB of the VM's RAM"
is a much stronger claim.
Sometimes that stronger claim is not justified, and I would rather have the tool acknowledge that limitation than quietly manufacture a level of precision that the underlying data doesn't support.
The rule I ended up with
I now think a good monitoring tool should follow a fairly boring rule:
"Never make a number look more precise than the measurement actually is."
For Wisely, that means making scope visible and preserving the path that a value took from the original observation through its scope, source, freshness, and confidence, before turning it into an interpretation or recommendation.
The important thing is that every step can introduce assumptions.
A direct observation is not the same as an attribution, an attribution is not the same as an estimate, and an estimate is not the same as a diagnosis. A correlation is even further away from a causal explanation.
The tool should make those boundaries visible instead of smoothing them away because the resulting dashboard looks cleaner.
There is still a lot I don't know
This isn't a finished answer to WSL2 memory behavior, and I don't want to pretend that it is.
There are still several questions I want to investigate. How far can VM-level memory be decomposed reliably from inside the guest? How useful can process attribution become without pretending to provide ownership that Linux doesn't expose directly? How should different WSL versions affect interpretation? How should Docker, multiple distributions, and long-running services change the model?
There is also a more experimental question: can some of these measurements be validated against controlled workloads strongly enough to make the resulting estimates more useful?
And perhaps the most important question for the project is this:
"Can a tool be genuinely useful while deliberately refusing to answer questions for which its measurements are insufficient?"
I don't know yet.
I'm building Wisely partly to find out.
What's actually next
I could list features here — more scopes, more attribution modes, a watch mode, whatever. I'm deliberately not going to do that.
The honest next step for Wisely isn't a feature. It's finding out whether anyone other than me finds it useful at all, unprompted, without me asking a favor.
Everything past that — history, sourced recommendations, verified actions — waits until that question has an answer.
Not because those ideas aren't good, but because building carefully on a guess that nobody has tested yet is exactly the trap this whole piece has been about.
So if you try it and it tells you something useful, something wrong, or something you had to squint at to understand, that's the report I actually need right now.
Not a feature request.
Where this leaves me
I started with a number that looked wrong: 8 GB in Windows and around 2 GB in Linux.
The first instinct is to look for the bug, but the more useful step turned out to be asking what each number actually meant before trying to reconcile them.
That eventually led me away from the idea of building a tool that simply collects more metrics. Instead, I'm trying to build a tool that is explicit about the limits of the metrics it collects and about the conclusions those metrics can legitimately support.
That sounds less impressive.
I think it is actually more useful.
The hard part of system monitoring is not always getting another number. Sometimes it is recognizing that two perfectly valid numbers should never have been compared in the first place.
That is the problem I am exploring with Wisely.
I don't know yet whether the approach is enough, but I think the question is worth investigating.
And that's where I'm taking the project next.
Further reading
Sources referenced or useful for going further:
- Microsoft Learn — Advanced settings configuration in WSL (.wslconfig, wsl.conf, autoMemoryReclaim)
- Microsoft Command Line blog — Windows Subsystem for Linux September 2023 update
- proc_meminfo(5) — Linux manual page
- proc_pid_statm(5) — Linux manual page
- proc_pid_smaps(5) — Linux manual page
- The /proc filesystem — Linux kernel documentation (smaps_rollup)
- proc_loadavg(5) — Linux manual page
- microsoft/WSL on GitHub
- Wisely on GitHub
Top comments (0)