glibc malloc is derived from ptmalloc2, but after long-term evolution, you cannot treat the flowcharts from early ptmalloc2 as the complete current implementation. The most useful way to understand it is to distinguish three layers: objects owned by the application, chunks and caches managed by the allocator, and OS mappings and physical residency.
Object Size vs. Chunk Size
malloc receives a request in bytes, but internally it must also satisfy alignment, metadata, and size class requirements. Therefore, the space reserved by the allocator for a 33-byte object may be larger; the difference belongs to internal overhead or internal fragmentation.
Applications can only access the valid object range according to the interface contract. You cannot write out of bounds to metadata just because a particular allocation seemed to provide a bit more space. calloc checks for overflow in the product of element count and size, whereas manually writing malloc(n * size) may already overflow before the call. realloc should preserve the original pointer if it fails for a non-zero target size; do not directly overwrite the only reference. malloc(3)
Expansion Requires Calculation Before Transferring Ownership
realloc may move the object; upon success, the old pointer and any pointers pointing into the interior of the old object must no longer be used. On failure, if the target size is non-zero, the old object remains owned by the caller. The example below combines the multiplication check, the non-zero convention, and temporary pointers into a single runnable example.
Save as resize-demo.c, compile with cc -Wall -Wextra resize-demo.c -o resize-demo, and the output should be 7 9 11 13. count=0 is explicitly rejected by the interface to avoid mixing the complex realloc(ptr, 0) behaviors across various standards and implementations into this "preserve old object or successfully expand" contract. realloc(3)
This small interface only demonstrates the update of the owning pointer; it does not manage logical length and capacity for the caller. After successful expansion, the caller should also submit the new capacity. If there are steps that may fail, such as element construction, a more complete rollback or commit design is needed. Rejection of zero length and overflow is deterministically reproducible; true allocation failures due to lack of memory should be verified using controlled fault injection, not by gambling on system behavior by requesting a huge object.
tcache and Arena Are Two Layers
tcache is a per-thread cache; suitable requests can be directly reused from it, reducing the number of times the arena is entered. The arena manages allocation state and free structures over a larger scope, usually with synchronization protection; multiple threads can share one arena, and one arena does not equate to one independent application object.
"The one arena per thread, therefore completely lock-free" is inaccurate. Thread cache hits and arena contention must be measured separately; cross-thread releases also affect which layer an object is cached in and how long it takes to become available again.
The specific sizes tcache can cache, the number of bins, the arena limits, and tuning interfaces vary by version. Newer source code already contains tcache classifications for larger chunks, while some manuals still describe earlier ranges; check the source code and documentation corresponding to your local build. Do not write down rules of thumb like "tens of bytes, 1 KiB, or CPU count times a constant" as invariant rules for all glibc versions.
What Do Free Structures Optimize?
Structure
Basic Purpose
Distinctions to Keep in Mind
tcache
Thread-local reuse
Not equal to the arena's own bins
fastbins
Fast linking for suitable small chunks, delayed merging
Merging is usually deferred to paths like consolidation
unsorted bin
Gives newly freed blocks a chance for recent reuse
Not all frees enter it first
small bins
Manages blocks for precise size classes
Class width and boundaries depend on target configuration
large bins
Organizes lookup by larger sizes
Cannot be generalized as a pure FIFO queue
top chunk
Splittable space at the end of the arena
Different from independent holes in bins and independent mmap blocks
The general path is to try suitable caches and existing free space first, splitting, merging, or requesting more from the system when necessary. The actual branch order depends on size and version; you cannot draw a unified flowchart like "first check 128 KiB, above is definitely mmap, below is strictly sequential bin scanning." glibc malloc source code
Who Gets Ownership on free?
In the diagram, addresses identify storage locations, and #numbers identify one lifecycle of an object. After release and reallocation, the address can be the same while the number is different; the old pointer does not automatically regain validity just because the new object happens to occupy the same address. Caching reduces repeated requests, but the number of retained blocks may diverge from the number of active application blocks.
Preparing the visual
Who Gets Ownership on free?
free ends the application's access to the object, but the block may remain in the allocator. Re-allocation can reuse the address but establishes a new object lifecycle.
{"id":"allocator-reuse-layers","title":"Who Gets Ownership on free?","summary":"free ends the application's access to the object, but the block may remain in the allocator. Re-allocation can reuse the address but establishes a new object lifecycle.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"alloc\" data-i18n=\"alloc\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><p class=\"hint\" data-i18n=\"slots\"></p><div id=\"slots\" class=\"choices\"></div><div class=\"track-row\"><span data-i18n=\"cache\"></span><div id=\"cache\" class=\"track\"></div></div><div class=\"track-row\"><span data-i18n=\"arena\"></span><div id=\"arena\" class=\"track\"></div></div><div class=\"metrics\"><div><span data-i18n=\"live\"></span><strong id=\"live\"></strong></div><div><span data-i18n=\"reserved\"></span><strong id=\"reserved\"></strong></div><div><span data-i18n=\"source\"></span><strong id=\"source\"></strong></div></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n.choices{grid-template-columns:repeat(2,minmax(0,1fr))}#cache,#arena{display:grid;grid-template-columns:repeat(4,minmax(0,1fr))}.metrics strong{font-size:17px}#source{min-height:5em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let live={},cache=[],arena=[0,1,2,3],generation=0,last='none';for(let i=0;i<4;i++){const b=document.createElement('button');b.dataset.chunk=i;b.onclick=()=>{if(live[i]){delete live[i];(cache.length<2?cache:arena).push(i);draw()}};$('#slots').append(b)}function draw(){document.querySelectorAll('[data-chunk]').forEach(e=>{const i=+e.dataset.chunk;e.textContent='0x'+(4096+64*i).toString(16)+' · '+(live[i]?'#'+live[i]:'∅');e.disabled=!live[i];});for(const k of ['cache','arena']){const a=k==='cache'?cache:arena;cells(k,Array.from({length:4},(_,i)=>({text:i<a.length?'C'+a[i]:'—',cls:i<a.length?'on':'empty'})));}set('live',Object.keys(live).length);set('reserved',4);set('source',t(last));$('#alloc').disabled=Object.keys(live).length===4;}$('#alloc').onclick=()=>{last=cache.length?'cache':'arena';const i=cache.length?cache.pop():arena.shift();if(i!==undefined)live[i]=++generation;draw()};$('#reset').onclick=()=>{live={};cache=[];arena=[0,1,2,3];generation=0;last='none';draw()};draw();","audio":false,"strings":{"active":"Application holds","alloc":"Allocate an object","arena":"Arena free","cache":"Thread cache","heading":"Who Gets Ownership on free?","intro":"Allocate and release for a fixed size class, and observe the thread cache and subsequent reuse.","live":"Active block count","none":"Not yet allocated","reserved":"Allocator reserved block count","reset":"Reset","scope":"Abstract single-thread cache capacity 2, arena has 4 blocks of the same class; does not simulate glibc's specific bin order, block headers, or system requests. The initial four blocks are always retained.","scopeLabel":"Model Scope and Conventions","slots":"Click active objects to free","source":"Last allocation source"}}
mmap Threshold Is Not a Static Boundary
The main arena often expands via the program break, while other arenas can establish mmap-backed regions; independent large mmap allocations serve yet another purpose. Small objects may also reside in mmap-backed arenas.
glibc can dynamically adjust the mmap threshold. Typical adjustments occur when releasing larger mmap chunks that meet conditions, raising the threshold to the corresponding size to avoid repeated requests to the system and un-mapping similar blocks; it does not lower the threshold every time a large block is freed.
Explicitly configuring the corresponding threshold affects dynamic adjustment. The threshold is just one policy; it must be understood in conjunction with existing reusable space and other constraints. See glibc allocation tunables for relevant names and meanings.
Ordinary private anonymous mmap still follows COW (Copy-On-Write) after fork; it is not a copy-once shared mapping. Independent blocks are easily un-mapped individually, which does not mean the system has no fragmentation or page table costs.
Why RSS Might Not Drop After free
After the application releases an object, the allocator may retain the chunk in the cache or arena for reuse by the next request. Even if some bytes are free, if the page they reside in still has active objects, it may not be possible to return the entire page.
For example, if a page has interleaved long-lived small objects and short-lived objects, after all short-lived objects are released, the remaining long-lived objects still keep this page. Simply summing all free bytes cannot deduce the number of returnable pages; this is an external fragmentation problem caused by layout and lifetime.
malloc_trim attempts to release free memory that can be released, possibly by shrinking or releasing page contents. It does not guarantee that all free space in all arenas is returned, nor does it make active objects disappear. Frequent trimming may increase subsequent page fault and reallocation costs. malloc_trim(3)
Calculate Free Bytes First, Then Returnable Pages
In a model that ignores metadata, each of the 4 pages has 1 KiB of long-lived objects and 3 KiB of short-lived objects. After the short-lived objects are released, the active amount is 4 KiB and the free amount is 12 KiB, but every page still has active data. If objects of the same lifetime are grouped, the 4 KiB of active content occupies only one page, and the other three pages become candidates for whole-page recycling.
This is not a promise that glibc will strictly place four objects as shown in the diagram, nor does it justify moving arbitrary malloc objects: externally saved pointers prevent ordinary C allocators from freely compacting active objects like some GCs. For a batch of objects with a clear shared lifecycle, application-level region allocation can make releasing the entire batch easier; if a few objects in it have a particularly long lifetime, the entire region may be held by them.
12 KiB Free, Why Can't We Recycle Even One Page?
The number of objects and the number of freed bytes are exactly the same for both layouts; the difference lies only in whether long-lived objects occupy every page. Release in the interleaved layout first, then switch to the grouped layout and redo it. Observe that while the free amount is the same, the number of whole-page candidates changes from 0 to 3. Ordinary malloc cannot arbitrarily move long-lived objects; layout usually needs to start from allocation strategy and object lifetime.
Preparing the visual
12 KiB Free, Why Can't We Recycle Even One Page?
The sum of free bytes does not determine the number of recyclable whole pages; a few long-lived objects scattered across different pages will cause every page to be retained.
{"id":"allocation-page-fragmentation","title":"12 KiB Free, Why Can't We Recycle Even One Page?","summary":"The sum of free bytes does not determine the number of recyclable whole pages; a few long-lived objects scattered across different pages will cause every page to be retained.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"layout\"><button data-mode=\"mixed\" data-i18n=\"mixed\"></button><button data-mode=\"grouped\" data-i18n=\"grouped\"></button></div><div id=\"heap\" class=\"heap\"></div><div class=\"actions\"><button id=\"release\" data-i18n=\"release\"></button><button id=\"restore\" data-i18n=\"restore\"></button></div><div class=\"metrics\"><div><span data-i18n=\"live\"></span><strong id=\"live\"></strong></div><div><span data-i18n=\"free\"></span><strong id=\"free\"></strong></div><div><span data-i18n=\"pages\"></span><strong id=\"pages\"></strong></div><div><span data-i18n=\"held\"></span><strong id=\"held\"></strong></div></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n.heap{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.heap section{border:1px solid var(--rule);padding:8px;border-radius:8px}.heap header{font-size:12px;margin-bottom:6px}.objects{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:4px}.objects .cell{min-height:55px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let layout='mixed',released=false;function draw(){pressed(layout);$('#heap').replaceChildren(...Array.from({length:4},(_,p)=>{const s=document.createElement('section'),h=document.createElement('header'),grid=document.createElement('div');h.textContent='Page '+p;grid.className='objects';for(let i=0;i<4;i++){const long=layout==='mixed'?i===0:p===0,e=document.createElement('div');e.className='cell '+(long?'on':released?'empty':'gold');e.textContent=t(long?'long':released?'empty':'short');grid.append(e)}s.append(h,grid);return s}));const n=released?(layout==='grouped'?3:0):0;set('live',released?4:16);set('free',released?12:0);set('pages',n);set('held',4-n);$('#release').disabled=released;$('#restore').disabled=!released;}document.querySelectorAll('[data-mode]').forEach(e=>e.onclick=()=>{layout=e.dataset.mode;released=false;draw()});$('#release').onclick=()=>{released=true;draw()};$('#restore').onclick=()=>{released=false;draw()};draw();","audio":false,"strings":{"empty":"Free","free":"Free (KiB)","grouped":"Grouped by Lifetime","heading":"12 KiB Free, Why Can't We Recycle Even One Page?","held":"Pages with Active Objects","intro":"Switch the initial layout of objects, then release or restore short-lived objects.","layout":"Initial Allocation Layout","live":"Active (KiB)","long":"Long-Lived","mixed":"Interleaved Lifetimes","pages":"Whole-Page Recycling Candidates","release":"Release Short-Lived Objects","reset":"Reset","restore":"Reallocate Short-Lived Objects","scope":"4 pages, each with 4 1 KiB objects, ignoring metadata. Switching layout represents a controlled run of reallocation, not moving C objects that are still referenced in place. Whole-page free is just a recycling candidate.","scopeLabel":"Model Scope and Conventions","short":"Short-Lived"}}
Lock Contention, False Sharing, and Leaks Are Different
Multiple threads competing for the same arena mutex is lock contention; two threads modifying different objects on the same cache line generating coherence traffic is typical false sharing. Limiting the number of arenas may reduce reserved memory but increases lock contention; it cannot be treated as a universal option to solve both problems simultaneously.
Leaks usually refer to losing necessary release paths or objects that are no longer needed but continuously retained; the allocator retaining free blocks is a different layer of phenomenon. When judging, compare the amount of active objects, allocator statistics, RSS/PSS, and load phases. See Process Memory Layout.
This is a glibc-specific interface that outputs XML information about arenas, etc. It should not be understood as a complete, precise business survival list of all thread cache objects. malloc_stats and mallinfo2 also have their own statistical scopes and limitations.
These two commands are used for separate control experiments and do not imply they should be deployed simultaneously to all programs. Record throughput, tail latency, allocation call stacks, and memory curves; avoid evaluating long-term services solely based on RSS at the end of execution.
The MALLOC_TRACE environment variable does not guarantee that the current glibc automatically records all allocations; enabling mtrace and debug library requirements must be configured according to the version. Ordinary out-of-bounds and use-after-free issues should be prioritized for localization via ASan or Memcheck; lowering thresholds cannot be treated as fixing memory errors.
Observation Requires Sampling Along Load Phases
Phase
Application Records
Allocator & System Side Records
Used to Judge
Pre-warmup
Initial active object count and bytes
Arena info, RSS/PSS
Baseline
Peak
Size distribution, peak concurrency, lifetime
Reserved space and residency
What drives the peak
After releasing short-lived objects
How many released, which remain
Free structures and RSS/PSS
Whether object release translates to whole-page recycling
Next round of same load
New request volume, allocation latency
Whether reused, whether growing again
Whether cache is stably reused
If business active bytes drop back to baseline, RSS remains high, but the next peak does not grow, the reserved space may be playing a reuse role. If the business active amount continues to rise in the same loop, first find the holders and release paths. If the active amount is stable but system usage continues to rise, then check fragmentation, thread/arena count, mappings, and statistical blind spots; do not announce a leak based solely on one curve.
malloc_info can output information for multiple arenas, but it is not a business object ledger. Sampling itself also has costs; do not format the entire XML after every malloc and then treat the resulting time as the allocator's raw latency. malloc_info(3)
What to Compare When Replacing the Allocator
The current design of Google TCMalloc can use per-CPU caches and has different modes; it and the tcmalloc in gperftools cannot be conflated into the same version just by library name. jemalloc uses mechanisms such as arenas, thread caches, and extents, and its statistics and recycling behaviors are configurable. TCMalloc Design, jemalloc Manual
There is no "large allocation fixed threshold, fragmentation high/low ranking" that holds stable across all loads. You should test object size distribution, lifetime, cross-thread releases, concurrency, and RSS curves. Heap profilers are not equal to CPU profilers; report dimensions need confirmation.
LD_PRELOAD only works for applicable dynamic linking and symbol resolution paths; static linking, custom allocation interfaces, restricted execution environments, and multiple runtimes may change the effect. Allocation and release must be paired by compatible allocator protocols; you cannot switch malloc on one side while letting other components use a mismatched free on the other.