AlgoMaster Logo

VSZ, RSS, PSS, and What They Mean

35 min readUpdated August 7, 2026
Listen to this chapter
Unlock Audio

A production dashboard reports that an API process has:

Is the process using 4.2 GiB, 620 MiB, or 280 MiB?

All three numbers can be correct. They measure different parts of the process:

  • VIRT describes its virtual address space.
  • RES describes pages currently resident in physical memory.
  • Heap usage is an application-runtime measurement covering only the managed heap.

Memory investigations often go wrong because numbers with different scopes are compared as if they measured the same thing. A large virtual address space is mistaken for high RAM usage, shared pages are counted once per process, or a heap dump is expected to explain the entire process footprint.

This chapter develops a precise working model for the most common process memory metrics: VSZ, RSS, PSS, and USS. It also shows how to inspect them on Linux and how to choose the right metric for a real diagnosis.

One Process, Several Valid Totals

A process can reserve an address range without having physical memory behind every byte. It can map a file whose pages enter memory only when accessed. It can share library code with many other processes. Its allocator can retain freed memory for later reuse.

These possibilities produce several overlapping views:

This is a conceptual relationship, not a claim that each metric is obtained by subtracting one displayed number from another. The kernel accounts for mappings page by page, and monitoring tools may use different sources or update schedules.

A useful first approximation is:

The details behind those sentences matter.

VSZ: Virtual Size

VSZ, also displayed as VIRT by some tools, is the total size of the virtual memory regions mapped into a process. Linux exposes a closely related value as VmSize in /proc/<pid>/status.

VSZ can include:

  • Executable code and data
  • Shared libraries
  • The heap and anonymous mappings
  • Thread stacks and their reserved ranges
  • Memory-mapped files
  • Shared-memory mappings
  • Address ranges reserved by a runtime or allocator

Suppose a process maps a 2 GiB file but reads only one 4 KiB page from it. The mapping can add roughly 2 GiB to VSZ, while only a small amount becomes resident because of that access.

Similarly, this call may create a large virtual allocation:

If it succeeds, the process has obtained a range of usable addresses. That does not necessarily mean the operating system immediately assigned and initialized 1 GiB of physical memory. The allocator may have reserved an address range that acquires physical backing as the program writes to it.

Therefore:

VSZ measures address-space coverage, not current RAM consumption.

A large VSZ is not automatically a leak or a problem. Managed runtimes, database engines, sanitizers, sparse data structures, and memory-mapped storage can all reserve large ranges intentionally.

VSZ is still useful. A steadily growing value can reveal accumulating mappings, unbounded address-space reservations, or mappings that are never removed. It is also relevant when investigating address-space exhaustion on systems with limited address ranges.

On Linux, VmPeak records the peak virtual size observed for the process. Unlike a current value, a peak is historical and does not fall when memory is released.

RSS: Resident Set Size

RSS, or resident set size, is the amount of memory currently resident in physical RAM and mapped into a process. Tools commonly display it as RSS or RES; Linux exposes VmRSS in /proc/<pid>/status.

If the process reserves 1 GiB but touches only 100 MiB, its VSZ may increase by approximately 1 GiB while its RSS increases by approximately 100 MiB.

Linux breaks VmRSS into three useful components:

RssAnon is resident anonymous memory. It commonly includes heap and stack pages, although the exact origin of anonymous mappings can vary.

RssFile is resident memory backed by regular files. Executable code, shared libraries, and memory-mapped files commonly contribute here.

RssShmem is resident shared memory, including shared anonymous mappings and tmpfs-backed memory.

These components answer a better question than RSS alone. For example, growth in RssAnon suggests a different investigation from growth in file-backed mappings.

RSS Counts Shared Pages in Every Process

Suppose four worker processes each have:

Each worker's RSS is:

Adding the four RSS values gives:

But the physical memory represented by this simplified example is:

The 400 MiB RSS sum counted the same 40 MiB of shared pages four times. RSS is meaningful for one process, but naively adding RSS across related processes can substantially overstate their combined physical footprint.

RSS Is Not “Memory Actively Used”

A resident page is in RAM, but the process may not have accessed it recently. RSS does not distinguish a hot data structure used on every request from a cold page that has not been read for minutes.

RSS is therefore not the same as a working set. It is a current residency measurement.

RSS can also change for reasons other than allocation:

  • Accessing a previously untouched allocation can bring pages into RAM.
  • Reading a mapped file can make file-backed pages resident.
  • The operating system can remove eligible pages from the resident set.
  • Unmapping memory can remove pages from the process.

On Linux, the RSS values in /proc/<pid>/status and /proc/<pid>/statm are maintained efficiently and may be somewhat imprecise at a particular instant. /proc/<pid>/smaps performs a more detailed page-table walk and is the better source when a precise diagnostic snapshot matters.

VmHWM, or high-water mark, is the peak RSS observed for the process. It answers “how high did RSS get?” rather than “what is RSS now?” A peak does not decrease after memory is freed.

PSS: Proportional Set Size

PSS, or proportional set size, handles shared resident pages differently from RSS.

Private resident pages count fully toward a process's PSS. Each shared resident page is divided by the number of processes sharing that page.

Return to the four-worker example:

Each worker receives one quarter of the shared memory:

Adding PSS across the workers gives:

That total matches the physical memory represented in the simplified example.

Real PSS accounting is performed page by page. One library page might be shared by 40 processes, another by 3, and another resident in only one process. The kernel applies the appropriate fraction to each page and adds the results.

PSS is especially helpful for:

  • Multi-process servers with preforked workers
  • Several processes using the same libraries
  • Applications that share large memory-mapped datasets
  • Comparing the combined footprint of a process group

PSS is not a permanent ownership assignment. Its value can change when another process maps, unmaps, enters, or exits a shared page. The process being measured may not have allocated or freed anything.

USS: Unique Set Size

USS, or unique set size, is the resident memory private to one process. If the process exited, no other process would retain mappings to those pages in their current form.

For ordinary Linux mappings, diagnostic tools commonly approximate USS as:

Those fields come from /proc/<pid>/smaps or smaps_rollup.

USS answers a narrower question than RSS:

How much resident memory is exclusive to this process right now?

In the worker example, each worker has a USS of approximately 60 MiB because its 40 MiB of library pages is shared.

USS is not a standard single field in /proc/<pid>/status, and different tools may calculate it with small variations. It also should not be interpreted as an exact prediction that this many bytes will instantly become free if the process exits. Kernel caches, file-backed pages, and other accounting scopes affect what the system can reuse and when.

Comparing the Four Metrics

MetricQuestion it answersTreatment of shared resident pagesCommon Linux source
VSZHow much virtual address space is mapped?Included in every mapping's virtual sizeVmSize, ps VSZ, top VIRT
RSSHow much resident memory is mapped into this process?Counted fully in every processVmRSS, ps RSS, top RES
PSSWhat is this process's fair share of resident memory?Divided among processes sharing each pagePss in smaps or smaps_rollup
USSHow much resident memory is unique to this process?ExcludedPrivate fields in smaps or smaps_rollup

No metric is universally best. The correct choice depends on the question.

Loading simulation...

Reading Memory Metrics on Linux

Linux exposes process information through /proc. Replace PID in the following commands with the numeric process identifier.

A Fast Overview with /proc/<pid>/status

Example output might look like:

This tells us:

  • The current and peak virtual sizes are about 4.2 GiB.
  • Current RSS is lower than the historical RSS peak.
  • Anonymous memory is the largest resident component.
  • Some private anonymous memory is currently in swap.

VmSwap does not mean that every nonresident virtual page is swapped out. On Linux it covers swapped private anonymous memory for that process and excludes some categories, such as swapped shared memory.

The values in this interface are labeled kB. When comparing tools, check their units and conversion conventions; a value displayed in MiB may have been converted and rounded.

A Detailed View with smaps

/proc/<pid>/smaps contains one section per memory mapping. A section includes fields such as:

Size is the mapping's virtual size. Rss is its resident portion, and Pss is the proportionally attributed resident portion.

The clean and dirty fields add another useful dimension:

  • A clean file-backed page matches its backing file and can generally be discarded and read again later.
  • A dirty page contains changes that must be preserved before its physical frame can be reused.
  • A private page is currently accounted exclusively to this process.
  • A shared page is accounted as shared at the time of the scan.

These labels describe current page state. A mapping created with a shared API is not guaranteed to have every resident page reported as shared at every instant; accounting depends on how many page mappings actually reference it.

Because smaps reports every mapping, it is useful when locating the source of memory. For example, a growing anonymous region, a mapped database file, and a large shared-memory object appear as distinct ranges.

The detail has a cost. Reading smaps requires the kernel to inspect the process's mappings and page tables, so high-frequency polling of many large processes can create measurement overhead.

A Practical Aggregate with smaps_rollup

When only process-wide totals are needed, use:

Typical fields include:

smaps_rollup aggregates the corresponding values across mappings. It is usually the most convenient source for total PSS and an approximate USS:

For the example:

Access to another process's smaps files can be restricted by process ownership, security settings, containers, or privileges.

Using ps, top, and pmap

For a quick snapshot:

For a live view, top commonly displays:

SHR is not the same as “pages definitely shared with another process right now.” It commonly reflects file-backed and shared-memory categories that may be shareable. Consequently:

is not a reliable calculation of USS. Use smaps_rollup when unique or proportional accounting matters.

To inspect mappings with summarized sizes:

If the smem utility is installed, it can present USS, PSS, and RSS for multiple processes. As with any monitoring tool, confirm which kernel fields and units the installed version uses.

A Hands-On Allocation Experiment

The distinction between virtual size and resident size becomes obvious when observed directly.

The following program:

  1. Pauses at its baseline.
  2. allocates a large block without touching it.
  3. writes one byte per system page.
  4. frees the allocation.

It accepts the allocation size in MiB and defaults to 256 MiB.

Save it as memory_metrics.c, then compile it:

Run it with a modest allocation first:

The program prints its PID. In another terminal, replace PID and run this command at every stage:

For a more detailed snapshot:

Stage 1: Baseline

The program already has code, libraries, a stack, allocator state, and other mappings. Record both VmSize and VmRSS.

Stage 2: Allocated but Untouched

After malloc(), virtual size will commonly increase by roughly the allocation size, while RSS increases very little.

The allocator has created or extended a usable address range, but the program has not written to most of it. A small RSS change is normal because allocator metadata and a few boundary pages may be touched.

The exact VSZ change is allocator-dependent. A request can come from an existing free region rather than a new operating-system mapping.

Stage 3: One Write per Page

The loop writes one byte in each system-sized page of the allocation. RSS and RssAnon should rise substantially.

Writing every byte is unnecessary for the experiment. One write per page is enough to make each covered page relevant to the process's resident anonymous footprint.

The increase may not equal the requested size exactly. Measurements happen at different instants, the allocator adds metadata, and the process has other activity. The important result is the direction and scale of the change.

Stage 4: Freed

After free(), one of two broad outcomes is common:

  • RSS drops significantly because the allocator returns a large region to the operating system.
  • RSS stays high because the allocator retains the region for later allocations.

Both outcomes can be valid. free() transfers ownership from the application back to its user-space allocator. It does not universally promise that the allocator will immediately remove the mapping or release every resident page.

Try a larger allocation only in a disposable environment with sufficient memory. Passing 1024 reproduces the 1 GiB experiment, but a smaller size is safer on laptops, development containers, and shared machines.

Why free() May Not Reduce RSS

Applications often treat “allocated” and “freed” as logical ownership states. RSS measures operating-system residency, which is a different layer.

A general-purpose allocator may retain freed blocks because:

  • Reusing a block is faster than requesting memory again.
  • A free block is surrounded by live allocations and cannot be returned as one suitable region.
  • Per-thread caches or allocator arenas keep memory ready for local reuse.
  • The allocator releases memory only after internal thresholds are reached.

Large allocations are often handled differently from small ones, so freeing one large block may reduce RSS while freeing thousands of small objects does not.

This distinction is essential when interpreting a service after a traffic spike:

That pattern does not by itself prove a leak. A leak means unreachable or unwanted allocations continue to remain owned. Retained allocator capacity may be reusable by future requests.

Runtime Heap Size vs. Process RSS

Managed runtimes expose useful metrics such as:

Those values describe the runtime's managed heap. Process RSS can additionally include:

  • Native allocations made by the runtime or libraries
  • Thread stacks
  • Runtime metadata
  • Generated or loaded code
  • Shared libraries
  • Memory-mapped files
  • Shared-memory regions
  • Allocator bookkeeping and retained free space

This explains a common observation:

The missing 600 MiB is not necessarily an error in either metric. It lies outside the particular heap measurement or reflects resident capacity not counted as live heap objects.

A heap dump usually explains objects managed by that runtime. It may not explain a native library leak, direct/native buffers, thread stacks, executable code caches, or memory mapped outside the managed heap.

Conversely, a runtime can reserve a very large potential heap, increasing VSZ, without making the entire reservation resident. “Maximum heap is 8 GiB” does not mean “the process currently occupies 8 GiB of RAM.”

The useful comparison is layered:

These values narrow the unexplained portion instead of forcing unrelated metrics to match.

Process Memory Inside a Container

Inside a Linux cgroup v2 container, the most relevant total for the container's memory accounting is usually:

memory.current reports the memory currently charged to that cgroup and its descendants. It is not VSZ, and it is not simply the sum of process RSS values.

The cgroup can be charged for categories including:

  • Anonymous user-space memory
  • File-backed memory and page cache
  • Kernel memory used on the cgroup's behalf
  • Page tables
  • Socket buffers
  • Shared memory

Inspect the breakdown with:

The exact cgroup path depends on where the process is placed and what part of the hierarchy is visible inside the container. Container platforms may also publish these values through their own metrics endpoints.

This corrects two misleading shortcuts:

Summed RSS can double-count pages shared by processes. Cgroup accounting also includes charged kernel and cache categories that may not appear in a managed heap metric.

When diagnosing a container approaching its memory limit, start with the cgroup total and its breakdown. Then use process PSS, RSS components, and application-runtime metrics to identify which processes and memory categories contribute to that total.

Measuring a Multi-Process Service

Consider a server with one coordinator and eight workers. The workers share program code and libraries, and they may share application data.

Adding their RSS values can count the same resident pages nine times. Looking only at the coordinator misses the workers. Looking only at the largest worker misses the total service footprint.

A more reliable workflow is:

  1. Define the complete process group or cgroup belonging to the service.
  2. Record RSS for per-process residency and outlier detection.
  3. Use PSS when attributing shared physical memory across the group.
  4. Use USS to find workers with unusually large private footprints.
  5. Compare the group with its cgroup accounting when it runs in a container.

PSS totals are particularly useful when comparing different worker counts. If four new workers mostly reuse shared pages, their total RSS can grow much faster than their total PSS.

USS can expose a single worker whose private anonymous memory grows while its peers remain stable. RSS alone may hide that difference behind a large shared baseline.

RSS vs. Reclaimability

Two processes can each have 1 GiB of RSS but place different demands on the system.

One process may hold mostly clean pages from a memory-mapped file. Those pages can often be discarded and read from the file again when needed.

Another may hold mostly dirty anonymous pages. Their contents cannot simply be forgotten; they must remain represented in RAM or suitable backing storage.

This is why RssAnon, RssFile, clean/dirty fields, and cgroup memory.stat can matter more than a single total. RSS tells how much is resident, while the breakdown helps explain what that resident memory represents.

It is still important not to turn these categories into absolute predictions. A page being technically reclaimable does not mean reclaiming it is free. Re-reading data creates I/O and latency, and the process may access the page again immediately.

Choosing the Right Metric

Use VSZ when investigating:

  • Reserved address-space growth
  • Large or accumulating mappings
  • Runtime reservations
  • Address-space exhaustion

Use RSS when investigating:

  • Current resident memory for one process
  • Anonymous versus file-backed residency
  • Per-process high-water marks
  • A sudden increase after pages are accessed

Use PSS when investigating:

  • Combined memory attribution across related processes
  • Prefork or worker-based servers
  • Shared libraries or shared mappings
  • Fair comparisons between process groups

Use USS when investigating:

  • Private resident growth in one process
  • Which worker adds unique memory
  • The lower bound of memory not currently shared with peers

Use cgroup memory metrics when investigating:

  • Container-level usage
  • Memory charged across a service and its descendants
  • Kernel, socket, cache, and anonymous contributions to a container total

For leaks, a time series is more useful than one snapshot. Track the relevant total and its components through a repeated workload:

Monotonic growth in private anonymous memory and application-owned allocations is a stronger signal than a single high VSZ or RSS value.

Measurement Pitfalls

Memory metrics are observations of a changing system. Treat them as samples, not immutable facts.

Comparing Unsynchronized Snapshots

The process can allocate, map, access, and free memory while files under /proc are being read. Values collected by separate commands may describe slightly different instants.

For trends, sample consistently. For a detailed incident snapshot, collect related fields close together and note the time.

Ignoring the Measurement Cost

Fast RSS counters are appropriate for frequent monitoring but can be approximate. smaps provides detailed mapping-level information at greater cost.

Use inexpensive metrics continuously and detailed scans selectively.

Mixing Current and Peak Values

VmRSS is current RSS. VmHWM is the historical peak. VmSize is current virtual size. VmPeak is its historical peak.

A current value can fall; its peak remains high. Dashboards should label these separately.

Mixing Units

Tools may report bytes, values labeled kB, MiB, GiB, or rounded human-readable units. Convert explicitly before comparing them:

Do not infer a memory discrepancy until the scopes and units match.

Summing RSS Across Processes

RSS counts shared pages once per process. Use PSS for proportional attribution or use cgroup/system totals for the containing scope.

Treating SHR as Subtractable Shared Memory

The SHR column in a process monitor is not a direct measurement of pages currently shared with other live processes. RES - SHR is therefore not a dependable USS calculation.

Summary

VSZ, RSS, PSS, and USS measure different scopes. VSZ is the size of mapped virtual address ranges, so it can be large without consuming equivalent physical RAM. RSS measures pages currently resident for one process, but counts shared pages fully in every process.

PSS divides each shared resident page among the processes using it, making it useful for multi-process services. USS estimates the resident pages unique to one process and can be derived from private fields in Linux smaps data.

On Linux, /proc/<pid>/status provides inexpensive current and peak metrics. smaps explains individual mappings, while smaps_rollup provides convenient totals for PSS and private memory. These snapshots can change while being read, and detailed scans cost more than fast counters.

A successful allocation can increase VSZ before it increases RSS. Touching one byte per page makes resident anonymous memory rise, while free() may or may not make RSS fall because user-space allocators can retain memory for reuse.

Runtime heap metrics cover only part of a process, and container accounting covers more than process RSS. Choose the metric whose scope matches the question, compare consistent units, and use trends plus component breakdowns instead of judging memory health from one large number.

Quiz

VSZ, RSS, PSS, and What They Mean Quiz

5 quizzes