Overview
jemalloc is a general-purpose malloc(3) replacement with emphasis on fragmentation avoidance and scalable multi-threaded concurrency. It was originally developed for FreeBSD, adopted by Firefox, and later became the default allocator for Rust. Meta uses it across their entire C++ server fleet.
The PascalAI binding exposes the full jemalloc API: raw allocation, aligned allocation, arena management, per-thread cache control, heap profiling, and statistics — all via a thin wrapper over libpai_jemalloc.so.
Requires libjemalloc-dev. Ubuntu/Debian: sudo apt install libjemalloc-dev
Key Characteristics
| Feature | Detail |
|---|---|
| Arena-based | Multiple independent arenas reduce lock contention across threads. Each arena manages its own memory independently. |
| Thread caching | Per-thread caches (tcaches) allow fast small allocations without any locking. Cache is flushed back to the arena when full. |
| ~250 size classes | Fine-grained size classes mean very low internal fragmentation — typically <5% waste for small objects. |
| Huge pages | Automatic transparent huge page support (2 MB pages) reduces TLB pressure for large working sets. |
| Heap profiling | Built-in allocation profiling with jeprof--enable-prof. |
| Statistics | Fine-grained per-arena and global stats: allocated, active, metadata, resident, mapped, retained bytes, and call counts. |
Performance
jemalloc consistently outperforms the system allocator in multi-threaded workloads and reduces resident memory usage through better fragmentation management.
| Scenario | Improvement |
|---|---|
| Multi-threaded contention | 2–10x less lock contention vs glibc malloc due to independent arenas per CPU |
| Fragmentation | Typically 10–30% less resident memory for long-running server processes |
| Large objects | Comparable to system malloc; advantage is in small/medium object churn |
Firefox (since 2007), Meta/Facebook (entire C++ backend), Redis, Apache Cassandra, HHVM, and Rust's default global allocator. This level of production validation makes jemalloc one of the most battle-tested allocators available.
vs Other Allocators
| Allocator | Comparison |
|---|---|
| tcmalloc (Google) | Both are arena-based with thread caching. jemalloc typically achieves lower fragmentation; tcmalloc is sometimes faster for workloads with many short-lived allocations. jemalloc has better heap profiling. |
| mimalloc (Microsoft) | mimalloc is newer and often faster in micro-benchmarks. jemalloc has years more production validation at scale and superior profiling tooling. |
| glibc malloc | glibc malloc uses a single global lock (or a small fixed number of arenas). jemalloc scales to many more threads without contention. |
Quick Start
Basic allocation and free
uses jemalloc;
{ Allocate 1024 bytes, use it, then free }
var ptr := Alloc(1024);
WriteLn('Allocated ' + IntToStr(AllocSize(ptr)) + ' usable bytes');
Free(ptr);
{ Allocate zeroed memory (safe for structs) }
var buf := AllocZero(64, 8); { 64 elements x 8 bytes each }
FreeSized(buf, 64 * 8); { faster free when size is known }
Aligned allocation (SIMD, DMA buffers)
uses jemalloc;
{ 64-byte alignment for AVX-512 SIMD operations }
var simdBuf := AllocAligned(64, 4096);
WriteLn('Aligned ptr: ' + IntToStr(simdBuf));
Free(simdBuf);
{ Resize an allocation (realloc-style) }
var p := Alloc(256);
p := Realloc(p, 512);
Free(p);
Arena per thread (isolate allocations)
uses jemalloc;
{ Create a dedicated arena and bind this thread to it }
var myArena := NewArena();
BindArena(myArena);
WriteLn('Thread using arena ' + IntToStr(CurrentArena()));
{ ... do work ... }
PurgeArena(myArena); { return dirty pages to OS }
DestroyArena(myArena); { must be empty before destroy }
Dump statistics
uses jemalloc;
{ Human-readable stats to stderr }
PrintStats();
{ Machine-readable JSON }
var json := StatsJSON();
WriteLn(Copy(json, 1, 120) + '...');
{ Structured stats record }
var s := GetStats();
WriteLn('Allocated : ' + IntToStr(s.Allocated) + ' bytes');
WriteLn('Resident : ' + IntToStr(s.Resident) + ' bytes');
WriteLn('Metadata : ' + IntToStr(s.Metadata) + ' bytes');
WriteLn('Version : ' + JEMallocVersion());
Types
TJEArena
An arena handle — an Integer index identifying a jemalloc arena. Returned by NewArena and CurrentArena. Pass to arena management functions.
TJEStats
Global allocator statistics snapshot returned by GetStats.
| Field | Type | Description |
|---|---|---|
| Allocated | Int64 | Bytes currently allocated by the application |
| Active | Int64 | Bytes in active memory pages (always ≥ Allocated) |
| Metadata | Int64 | Bytes consumed by jemalloc's own internal bookkeeping |
| Resident | Int64 | Bytes resident in physical RAM |
| Mapped | Int64 | Bytes mapped from the OS (includes reserved-but-unused) |
| Retained | Int64 | Bytes retained in virtual address space after being freed |
| SmallNMalloc | Int64 | Number of small allocation calls (cumulative) |
| SmallNDalloc | Int64 | Number of small deallocation calls (cumulative) |
| LargeNMalloc | Int64 | Number of large allocation calls (cumulative) |
| LargeNDalloc | Int64 | Number of large deallocation calls (cumulative) |
TJEArenaStats
Per-arena statistics returned by GetArenaStats(arena).
| Field | Type | Description |
|---|---|---|
| ArenaIndex | Integer | Index of this arena |
| NThreads | Integer | Number of threads currently bound to this arena |
| Allocated | Int64 | Bytes currently allocated from this arena |
| Dirty | Int64 | Bytes in dirty unused pages (awaiting purge or reuse) |
| Muzzy | Int64 | Bytes in muzzy pages (purged but not yet returned to OS) |
Allocation API
Core allocation functions. All pointers are returned as Int64 (raw address). You are responsible for tracking pointer lifetimes — jemalloc does not perform garbage collection.
| Function | Description |
|---|---|
| Alloc(Size) | Allocate Size bytes. Equivalent to malloc. Returns pointer as Int64. |
| AllocZero(Count, Size) | Allocate Count * Size bytes, zeroed. Equivalent to calloc. |
| Realloc(Ptr, NewSize) | Resize an existing allocation. Returns new pointer (may differ from input). |
| Free(Ptr) | Release memory. Equivalent to free. |
| FreeSized(Ptr, Size) | Release memory with known size. Faster than Free — skips size lookup. |
| AllocAligned(Alignment, Size) | Allocate with power-of-2 alignment. Use for SIMD buffers, DMA, cache-line alignment. |
| AllocSize(Ptr) | Return the usable size of an allocation. May exceed the requested size due to size class rounding. |
Arena API
Arenas are independent memory pools. Assigning each thread a dedicated arena eliminates all cross-thread locking for allocation-heavy workloads.
| Function | Description |
|---|---|
| NewArena | Create a new arena. Returns a TJEArena handle. |
| CurrentArena | Return the arena index that the current thread is bound to. |
| BindArena(A) | Bind the current thread to arena A. All subsequent allocations use this arena. |
| DestroyArena(A) | Destroy an arena. The arena must be empty (all allocations freed) before calling. |
| PurgeArena(A) | Return dirty unused pages in arena A to the OS. Reduces RSS at the cost of future page faults. |
| PurgeAll | Purge dirty pages from all arenas. Useful before a latency-sensitive phase begins. |
Thread Cache API
The per-thread cache (tcache) is jemalloc's primary mechanism for lock-free fast-path allocation. Small objects are served from the tcache without touching the arena at all.
| Function | Description |
|---|---|
| FlushThreadCache | Flush the current thread's tcache, returning cached memory to its arena. Call before thread exit or after a burst of allocations. |
| SetThreadCache(Enable) | Enable or disable the tcache for the current thread. Disabling forces all allocations through the arena (slower but more predictable latency). |
Statistics API
jemalloc maintains detailed internal counters updated atomically. Stats collection has negligible overhead and is safe to call in production.
| Function | Description |
|---|---|
| GetStats | Return a TJEStats record with global byte counts and allocation call counts. |
| GetArenaStats(A) | Return a TJEArenaStats record for a specific arena. |
| StatsJSON | Return the full jemalloc stats as a JSON string. Equivalent to malloc_stats_print with JSON output. Includes per-bin, per-arena, and global detail. |
| PrintStats | Print human-readable stats to stderr. Useful for quick debugging. |
Heap Profiling API
Heap profiling requires jemalloc built with --enable-prof. Output files are compatible with jeprof (a pprof-compatible tool) and can be visualized as flame graphs or call-graph PDFs.
| Function | Description |
|---|---|
| StartProfiling | Enable heap profiling. Samples allocations at a configurable rate (default: 512 KB). |
| DumpProfile(Path) | Write the current heap profile to Path. Can be called multiple times to capture snapshots at different points. |
| StopProfiling | Disable heap profiling and flush the final dump. |
uses jemalloc;
StartProfiling();
{ ... run your workload ... }
DumpProfile('/tmp/heap_before.heap');
{ ... more work ... }
DumpProfile('/tmp/heap_after.heap');
StopProfiling();
{ Analyze with: jeprof --pdf ./myapp /tmp/heap_after.heap > report.pdf }
Info API
| Function | Description |
|---|---|
| ArenaCount | Return the number of CPU arenas currently configured. Typically equals the number of CPU cores. |
| JEMallocVersion | Return the jemalloc version string, e.g. "5.3.0-0-g54eaed1d8b56b1aa528be3bdd1877e59c56fa90c". |
Package Info
System Requirements
libjemalloc-devUbuntu/Debian:
sudo apt install libjemalloc-dev
TJEStats Fields
.Allocated — bytes in use by app.Active — bytes in active pages.Metadata — allocator overhead.Resident — physical RAM usage.Mapped — total OS mapping.Retained — virtual space held.SmallNMalloc — small alloc count.SmallNDalloc — small free count.LargeNMalloc — large alloc count.LargeNDalloc — large free count
Functions
- Alloc / AllocZero
- Realloc
- Free / FreeSized
- AllocAligned
- AllocSize
- NewArena / CurrentArena
- BindArena / DestroyArena
- PurgeArena / PurgeAll
- FlushThreadCache
- SetThreadCache
- GetStats
- GetArenaStats
- StatsJSON / PrintStats
- StartProfiling
- DumpProfile
- StopProfiling
- ArenaCount
- JEMallocVersion