· via Hacker News – Front Page (native)
jemalloc 5.4.0 ships over 160 commits of fixes, refactoring and portability work
Version 5.4.0 of the jemalloc allocator lands with more than 160 commits of bug fixes, internal restructuring and portability work, plus pinned-memory tracking and an adaptive thread-cache policy.
What happened
jemalloc 5.4.0 has been released, and it is a housekeeping release at scale. According to the release notes published on GitHub, the update spans more than 160 commits aimed squarely at technical debt: internal restructuring, bug fixes, wider test coverage and the retirement of old options, alongside portability fixes submitted in response to upstream issue reports. The story reached Hacker News's front page on 18 September 2026.
New features
The headline addition is support for pinned memory. A new EXTENT_ALLOC_FLAG_PINNED flag lets custom extent-allocation hooks mark mappings that cannot be reclaimed — HugeTLB pages are the given example — so the allocator reuses them preferentially instead of routing them through the decay and purge machinery. A set of accompanying mallctl interfaces, including stats.pinned, per-arena and per-extent counters, and mutex statistics for the new path, makes that usage observable.
Elsewhere, per-CPU arena selection can now be resumed through thread.arena after a thread was pinned to a specific arena; the human-readable and JSON forms of the malloc statistics output have been brought into closer agreement; and the runtime experimental_infallible_new option has been replaced by a compile-time --enable-cxx-infallible-new switch, which the notes say lets the compiler optimize further, including in move constructors, and fixes the new(std::nothrow) contract.
The breaking change: adaptive thread caching
The one incompatible change concerns the thread cache. jemalloc now adapts per-bin fill and retention targets based on demand observed between garbage-collection events, discarding the previous fixed refill and flush policy. Seven legacy, non-experimental controls were removed as part of the switch: lg_tcache_nslots_mul, tcache_nslots_small_min, tcache_nslots_small_max, tcache_nslots_large, tcache_gc_delay_bytes, lg_tcache_flush_small_div and lg_tcache_flush_large_div. Matching malloc_conf settings are now ignored without warning, the corresponding opt.* mallctls return ENOENT, and tcache_ncached_max keeps working. Anyone shipping a malloc_conf string containing the old knobs should audit it before upgrading, because the failure mode is silent.
Notable fixes
The fix list touches correctness corners that matter to strict consumers. free, free_sized and free_aligned_sized now preserve errno, as does page purging based on process_madvise — relevant because code written to POSIX expectations often assumes library calls will not clobber it. free_sized and free_aligned_sized accept NULL, aligning with C23. Size-class numeric overflow checks were corrected, a potential deadlock during arena_reset was resolved, and duplicate opt.stats_print fields no longer appear in malloc_stats_print output.
Thread-specific-data lifecycle bugs were fixed on two fronts: thread-cache bins are initialized before the cache is marked enabled, which stops reentrant bootstrap allocations from touching uninitialized state, and TSD is no longer recreated for late deallocations after thread teardown on generic-TSD platforms. The THP sysfs file is now opened with O_CLOEXEC so it does not leak across exec, and a bug in the interaction between profiling sampling and guard pages in the SAN layer was repaired.
A structural overhaul
Internally, much of the release is restructuring. The front end in jemalloc.c was split up, with arena management, initialization, fork orchestration and allocation dispatch extracted into their own modules, while tcache and arena ownership was untangled and circular header dependencies were consolidated away. The control-plane code was reorganized by subsystem, statistics collection was refactored into separate gather and emission stages driven by descriptor tables, and the PAI vtable layer was deleted in favour of direct PAC and HPA calls. A base-block growth heuristic is now capped so that a rare race can no longer exhaust the process's virtual address space, background-thread lifecycle logic moved into its own module, and, most consequentially for future ports, a new OS abstraction layer isolates platform-dependent file and process I/O, time, synchronization, CPU, virtual-memory, atfork, error-handling and related operations from the allocator core.
Portability work
On the portability front the release fixes malloc_getcpu on macOS so it reads the current CPU number correctly, switches background-thread sleeps to CLOCK_MONOTONIC with configure-time detection of monotonic condvar support — preventing stalls when the system clock jumps backwards — repairs thread-exit TSD cleanup on MinGW, and silences GCC 16 and macOS build warnings. arena_s now uses a C99 flexible array member, rdtscp detection works again with --with-lg-vaddr, a libstdc++-internal std::__throw_bad_alloc call was replaced with standard C++, and PID-namespace symlink parsing no longer depends on glibc's strtok and atol, returning identifiers as uint64_t instead.
Why it matters
jemalloc sits underneath an enormous amount of production software, so quiet correctness fixes — errno preservation, the arena_reset deadlock, the TSD lifecycle edge cases — carry weight well beyond the commit count. The thread-cache change is the practical upgrade hazard: stale options disappear silently rather than loudly, which argues for checking embedded configuration before deploying. Longer term, the OS abstraction layer and the modularized front end lower the cost of porting jemalloc to new platforms and should make future changes easier to land, while the new pinned-memory controls give operators of HugeTLB-backed workloads visibility and control they previously lacked.
- #memory-management
- #systems-programming
- #allocator
- #open-source
- #c