When the Go runtime returns heap pages to the Linux kernel, which madvise mode does it use by default?
answer
- two ways to tell the kernel you are done
- one drops RSS now, one later
- the lazy one is cheaper but confusing
- GODEBUG=madvdontneed flips the choice
- the current Linux default is the eager one
basics
~20 sOn Linux the Go runtime uses MADV_DONTNEED by default, so RSS falls as soon as pages are released. Setting GODEBUG=madvdontneed=0 switches to MADV_FREE, which is cheaper but leaves RSS high until the kernel needs the memory.
solid answer
~40 sOn Linux, current Go releases pages with `MADV_DONTNEED`: the kernel drops them immediately and RSS falls the moment the runtime returns them. `GODEBUG=madvdontneed=0` selects `MADV_FREE` instead, which is more efficient because the kernel only reclaims the pages under memory pressure — but then RSS does not drop when the runtime releases, which is confusing on a dashboard. Older Go releases defaulted to `MADV_FREE` on Linux, and that is the origin of the folklore that a Go process never gives memory back. On the BSDs and illumos/Solaris the default is `MADV_FREE` and `GODEBUG=madvdontneed=1` forces `MADV_DONTNEED`. The diagnostic consequence matters: on a modern Linux deployment, a high RSS with a small live heap means the runtime has **not released** those pages, so look at retained idle spans rather than blaming lazy kernel accounting.
go deeper
You are not expected to know the madvise modes by name. Know only that the Go runtime does eventually hand freed pages back to the operating system rather than keeping them forever.
Be able to say that MADV_DONTNEED drops pages immediately so RSS falls, while MADV_FREE leaves them until the kernel wants memory so RSS does not, and that a GODEBUG setting selects between them.
Use it to sharpen a diagnosis. On a default Linux deployment a high RSS means the runtime has not released the pages, so stop blaming lazy kernel accounting and go read HeapIdle against HeapReleased.
Know that this default has changed across Go's history and that fleet memory dashboards and alert thresholds shifted with it. Tie any memory alert you set to the toolchain version the fleet actually runs.
## Two ways to tell the kernel you are done with a page When the Go runtime decides that a range of heap memory should go back to the operating system, it does not unmap it — unmapping and remapping address space is expensive and fragments the arena. Instead it uses `madvise` to tell the kernel what it may do with the pages, and there are two relevant advices: - **`MADV_DONTNEED`** — the kernel drops the pages immediately. RSS decreases at once. The next touch of that address range faults in a fresh zero page. - **`MADV_FREE`** — the kernel marks the pages as reclaimable but leaves them in place. It only takes them back when it is actually short of memory. RSS does **not** decrease at the time of the call. If the process touches the range again before the kernel reclaims it, the pages are still there and no fault is needed, which makes this the cheaper option. Both are correct. They trade promptness of accounting against the cost of reacquiring memory. ## What Go does On **Linux**, current Go defaults to `MADV_DONTNEED`. The `GODEBUG` setting `madvdontneed=0` switches it to `MADV_FREE`, and the runtime's own documentation frames the trade exactly as above: more efficient, but RSS numbers will drop only when the OS is under memory pressure. On the **BSDs and illumos/Solaris** the polarity is reversed: the default there is `MADV_FREE`, and `GODEBUG=madvdontneed=1` forces `MADV_DONTNEED` — less efficient, but RSS drops more quickly. This default has not always been the same on Linux. There was a period of Go releases in which `MADV_FREE` was the Linux default, and that period is the entire source of a piece of folklore that still circulates: *"a Go process never gives memory back to the OS"*. It was never quite true even then — the memory was reclaimable, the kernel simply had not bothered — but the graphs looked identical to a leak, and operators concluded the worst. Because the default changed, an answer to this question that does not say which era it is describing is only half right. ## Why this changes a diagnosis Suppose a service on Linux shows a small live heap and a stubbornly high RSS. There are two candidate stories: 1. The runtime released the pages and the kernel has not got round to reclaiming them. 2. The runtime has not released the pages at all — it is holding them as idle spans for reuse. On a modern Linux deployment with the default advice, story 1 is **not available**. `MADV_DONTNEED` reclaims immediately, so if RSS is high, the pages have not been released yet. That collapses the search space onto story 2, and the numbers to read are `HeapIdle` and `HeapReleased` in `runtime.MemStats`: a large `HeapIdle - HeapReleased` confirms retention. If someone has set `GODEBUG=madvdontneed=0` — or if the process runs on a BSD — story 1 comes back and RSS becomes a much weaker signal. A useful habit is to check the `GODEBUG` value the process was started with before spending time on its memory graph at all. ## Interaction with an explicit release `debug.FreeOSMemory` forces a collection and then attempts to return as much memory as possible. What "return" means at the syscall level is whichever advice is in effect. Under `MADV_DONTNEED` you see the RSS drop essentially immediately, which is what makes the one-shot canary experiment such a clean test. Under `MADV_FREE` the same call can appear to do nothing, and an engineer who does not know why will conclude, wrongly, that the memory was live. ## What to say in an interview The complete answer has three parts: name both advices and their difference, state that current Linux Go uses the eager one so RSS reflects releases promptly, and note that this default has changed across Go's history and is inverted on the BSDs. The payoff sentence is the diagnostic one — knowing which advice is in effect tells you whether a high RSS is the kernel being lazy or the runtime holding on.
- Why would anyone prefer MADV_FREE if it makes RSS look worse?Because it is cheaper. The kernel leaves the pages in place and only reclaims them under memory pressure, so if the process touches that range again before then it costs nothing — no page fault, no zeroing. For an allocation pattern that oscillates, that is a real saving. The price is paid entirely in observability: memory graphs stop tracking what the runtime is actually doing.
- On a Linux service running with the default settings, RSS is high while the live heap is small. What does the madvise default let you rule out?It rules out the story that the runtime already released the pages and the kernel is being lazy about reclaiming them. With MADV_DONTNEED the kernel drops pages at once, so a high RSS means the runtime has not released them yet. The remaining candidates are retained idle spans, memory that is still reachable, and memory outside the Go heap.
- Someone reports that debug.FreeOSMemory does nothing on their machine. What would you check first?Which madvise advice is in effect. If the process runs on a BSD or with GODEBUG=madvdontneed=0 on Linux, the pages are released with MADV_FREE and RSS will not move until the kernel wants the memory, so the call can look like a no-op while having worked perfectly. Check the GODEBUG value and the platform before concluding the memory was live.
saying these in an interview costs you the question
- Says Go never returns memory to the operating system
- Assumes MADV_FREE is still the Linux default
- Thinks the runtime unmaps memory instead of advising the kernel
- Cannot explain why RSS might lag a real release
- Treats RSS as authoritative without checking GODEBUG