Field note · macOS internals

macOS memory pressure: which number tells you a model won't fit

Three tools reported three different numbers for the same Mac at the same moment, and all three were correct.

Measured on a MacBook Pro, Apple M5 Pro, 48 GB, macOS 27.0 (build 26A5421a), Searoom 0.1.1. Page size 16384 bytes. One machine, one configuration. Counter values move between invocations, so readings taken seconds apart do not reconcile exactly.

At one moment on a 48 GB M5 Pro, three tools described the same memory state three different ways. memory_pressure said 84% free. Searoom said 52.7% used. vm_stat reported 246,914 free pages, which is about 4 GB, or roughly 8% of the machine.

None of them is wrong. They are answering different questions, and only one of those questions is the one you actually have when you are deciding whether a model will fit.

The same instant, three readings

These readings were taken within the same minute on an otherwise ordinary desktop session: browser, editor, Slack, a few helpers. No inference workload was running.

Baseline readings, M5 Pro 48 GB, macOS 27.0
SourceReadingWhat it is counting
memory_pressure84% freeA system-wide free percentage that treats most resident pages as reclaimable
Searoom52.7% usedActive plus wired plus compressed, as a fraction of physical memory
vm_stat free pages4.05 GBPages on the free list right now, holding nothing
kern.memorystatus_vm_pressure_level1The kernel's own pressure state: normal
vm.swapusage0 bytesSwap actually allocated

The spread between 84% free and 52.7% used is not a rounding difference. It is a disagreement about where the boundary sits between memory that is in use and memory you could still have.

Free memory is the least useful number

The free page count is the one most people reach for and the one that tells you least. A healthy macOS system drives free memory toward zero on purpose. Pages sitting on the free list are doing nothing. Pages holding file cache are doing something useful and can still be handed to you the moment you ask.

Apple's own definition points the same way. The Activity Monitor documentation describes the memory pressure graph as determined by free memory, swap rate, wired memory, and file cached memory together, not by any one of them, and Apple's guidance on whether a Mac needs more RAM reads that graph rather than a free-memory figure.

On the baseline reading above, free memory was about 4 GB while another 19.9 GB sat in inactive, speculative, and purgeable pages. Treating only the 4 GB as available would have been wrong by a factor of six.

What Searoom counts, and why

Searoom defines the working set as active plus wired plus compressed pages, capped at physical memory.

let used = min(total, active + wired + compressed)
let available = total - used
let cached = min(available, inactive + purgeable + speculative)

Each term is chosen for a reason. Active pages are in use and not immediately reclaimable. Wired pages cannot be paged out at all. Compressed pages are the compressor's footprint, which is real physical memory the compressor is occupying on behalf of data that no longer fits uncompressed.

The categories are not Searoom's invention. They are fields of vm_statistics64, declared in XNU's osfmk/mach/vm_statistics.h as active_count, wire_count, inactive_count, speculative_count, purgeable_count and compressor_page_count. That is the same structure vm_stat prints from, so every tool on this page is ultimately reading one set of counters and disagreeing only about how to combine them.

Inactive, purgeable, and speculative pages are excluded from the working set and reported separately as cached. They are resident, but macOS can reclaim them without stalling you.

This intentionally differs from total - free, and it may differ slightly from Activity Monitor. It is built to answer one question: if I ask for another 10 GB, is that plausible without the system fighting me.

Pressure is a state, not a percentage

Memory pressure in the macOS sense is not a utilisation ratio. It is a discrete kernel state, readable as an integer:

sysctl kern.memorystatus_vm_pressure_level
# 1 = normal, 2 = warning, 4 = critical

Those three values are the set Apple exposes to applications through Grand Central Dispatch, where a memory pressure dispatch source delivers DISPATCH_MEMORYPRESSURE_NORMAL, DISPATCH_MEMORYPRESSURE_WARN and DISPATCH_MEMORYPRESSURE_CRITICAL. The sysctl and the dispatch source are two views of one kernel state.

That state is the authoritative signal, because it reflects what the kernel has decided rather than what an observer inferred. Searoom reads it and also derives a level from working-set utilisation, then keeps whichever is more severe. The thresholds for the derived tier are 70%, 85%, and 95%.

Keeping the more severe of the two matters in both directions. A machine can be at 60% working set while the kernel is already in a warning state, and it can be at 90% working set with the kernel entirely relaxed. Reporting only one of those would be misleading half the time.

What happens when you actually take the memory

Rather than reason about this, I allocated 8 GiB (8.59 GB) in a separate process, touched every page so it was genuinely resident, held it, and re-read every counter.

Before and during an 8 GiB resident allocation
ReadingBaselineUnder allocation
Searoom working set27.18 GB30.93 GB
Searoom available24.36 GB20.61 GB
Searoom pressure value0.5270.600
memory_pressure free84%76%
Kernel pressure level1 (normal)1 (normal)
Free pages246,91414,725
Speculative pages52,981159
Purgeable pages33,2263,786
Compressor pages228,784444,924
Swap used00

Three things in that table are worth reading carefully.

The working set rose by 3.75 GB, not by the 8.59 GB I allocated. The compressor absorbed the difference: compressor pages grew by 216,140, which is 3.54 GB of additional compressor footprint holding considerably more logical data.

Free memory collapsed from 4.05 GB to 0.24 GB, and speculative pages went from 52,981 to 159. macOS reclaimed cache rather than refusing the allocation. If you were watching free memory, this looked like an emergency. Nothing was wrong.

Swap stayed at exactly zero and the kernel never left the normal state. The system absorbed a large allocation using compression and reclaim, which is the design working as intended.

Limit of this measurement

The allocated pages were zero-filled, which makes them close to perfectly compressible. That is why 8.59 GB of allocation produced only 3.75 GB of working-set growth. Model weights are not compressible in this way, so a 15 GB model will not shrink under the compressor the way this test did. Treat the mechanism here as real and the compression ratio as an artefact of the test data.

Which number answers your question

If the question is whether another model will load, read available memory and the kernel pressure state together, and treat sustained swap I/O as the signal that you have gone too far.

Swap allocation on its own is weak evidence. macOS can hold swap allocated long after the pressure that created it has passed. Swap throughput, measured as bytes per second of swap-in and swap-out, is what correlates with the stall you can feel. That distinction is large enough to deserve its own article.

The short version: available memory tells you what you can ask for, the pressure state tells you what the kernel thinks, and swap I/O tells you whether you already lost.

Reproduce this

Every number above came from tools already on your Mac, plus one Searoom command.

sysctl kern.memorystatus_vm_pressure_level
sysctl vm.swapusage
vm_stat
memory_pressure
Searoom --dump-sample

Searoom --dump-sample prints one JSON sample on stdout and exits. It takes two internal readings first, because rate-derived values such as swap I/O have no previous counter to subtract on a cold start and would otherwise report zero.

If you publish a result from this, name the Mac, the macOS build, the Searoom version, and the workload. A memory reading without those is not reproducible, and on this subject a number nobody can reproduce is worth very little.

The canonical definitions for every value discussed here live in the metric reference.

Sources