JEMALLOC(3)

๐Ÿ“ฆ
version 15.1 ยท 2 versions
, in freebsd 15.1

๐ŸŒŸ NAME ๐ŸŒŸ

okay so bestie, welcome to JEMALLOC(3) โ€” the User Manual page โ€” and the whole vibe is jemalloc - general purpose memory allocation functions. That's the name, that's the assignment, periodt. โœจ


๐Ÿ“š LIBRARY ๐Ÿ“š

This manual is literally describing jemalloc version 5.3.0-0-g54eaed1d8b56b1aa528be3bdd1877e59c56fa90c. More information can be found at the jemalloc website[1]. ๐Ÿซถ

The following configuration options are enabled in libc's built-in jemalloc: --enable-fill, --enable-lazy-lock, --enable-stats, --enable-utrace, --enable-xmalloc, and --with-malloc-conf=abort_conf:false. Additionally โ€” not me dropping extra lore โ€” --enable-debug is enabled in development versions of FreeBSD (controlled by the MK_MALLOC_PRODUCTION make variable). ๐Ÿ‘‘


โœจ SYNOPSIS โœจ

First things first, bestie: you gotta include these headers, no cap:

#include <stdlib.h>
#include <malloc_np.h>

๐Ÿ’– Standard API ๐Ÿ’–

Here's the standard API lineup, and yes, every signature is exact โ€” copy-paste energy only:

void *malloc(size_t size);

void *calloc(size_t number, size_t size);

int posix_memalign(void **ptr, size_t alignment, size_t size);

void *aligned_alloc(size_t alignment, size_t size);

void *realloc(void *ptr, size_t size);

void free(void *ptr);

๐Ÿฆ‹ Non-standard API ๐Ÿฆ‹

And here's the non-standard API, aka the extra-sparkle section. The way these declarations are giving main character energy, fr fr โ€” these exact signatures slay:

void *mallocx(size_t size, int flags);

void *rallocx(void *ptr, size_t size, int flags);

size_t xallocx(void *ptr, size_t size, size_t extra, int flags);

size_t sallocx(void *ptr, int flags);

void dallocx(void *ptr, int flags);

void sdallocx(void *ptr, size_t size, int flags);

size_t nallocx(size_t size, int flags);

int mallctl(const char *name, void *oldp, size_t *oldlenp, void *newp,
            size_t newlen);

int mallctlnametomib(const char *name, size_t *mibp, size_t *miblenp);

int mallctlbymib(const size_t *mib, size_t miblen, void *oldp,
                 size_t *oldlenp, void *newp, size_t newlen);

void malloc_stats_print(void (*write_cb) (void *, const char *),
                        void *cbopaque, const char *opts);

size_t malloc_usable_size(const void *ptr);

void (*malloc_message)(void *cbopaque, const char *s);

const char *malloc_conf;

๐Ÿ’… DESCRIPTION ๐Ÿ’…

okay bestie, buckle up, this is the part where we actually meet the functions ๐Ÿซถ

โœจ Standard API โœจ

  • malloc() โ€” gives you size bytes of uninitialized memory. no cap, it does NOT zero it out for you. the space it hands back is suitably aligned (like, after possible pointer coercion) so you can stash literally any type of object in there. icon behavior.
  • calloc() โ€” grabs space for number objects, each size bytes long. the result is identical to calling malloc() with an argument of number * size, except โ€” and this is the slay part โ€” the allocated memory is explicitly initialized to zero bytes. free cleanup, periodt.
  • posix_memalign() โ€” allocates size bytes such that the allocation's base address is a multiple of alignment, and returns the allocation in the value pointed to by ptr. the requested alignment must be a power of 2 at least as large as sizeof(void *). yes, that's a hard requirement, don't come for me.
  • aligned_alloc() โ€” allocates size bytes of memory such that the allocation's base address is a multiple of alignment. the requested alignment must be a power of 2. โš ๏ธ behavior is undefined if size is not an integral multiple of alignment โ€” so like, don't do that, bestie.
  • realloc() โ€” changes the size of the previously allocated memory referenced by ptr to size bytes. here's the tea: the contents are unchanged up to the lesser of the new and old sizes. if the new size is larger, the contents of the newly allocated portion are undefined ๐Ÿ™ˆ. upon success, the memory referenced by ptr is freed and a pointer to the newly allocated memory is returned. note that realloc() may move the memory allocation, resulting in a different return value than ptr โ€” so literally update your pointer, i promise it's not that deep. and if ptr is NULL, realloc() behaves identically to malloc() for the specified size. cute.
  • free() โ€” causes the allocated memory referenced by ptr to be made available for future allocations. if ptr is NULL, no action occurs. no drama, no crash, we love a graceful no-op ๐Ÿ’œ

๐Ÿฆ‹ Non-standard API ๐Ÿฆ‹

so this is the fun part. the mallocx(), rallocx(), xallocx(), sallocx(), dallocx(), sdallocx(), and nallocx() functions all have a flags argument that can be used to specify options. the functions only check the options that are contextually relevant (they're not out here doing extra work for no reason). use bitwise or (|) operations to specify one or more of the following ๐Ÿ‘‡

  • MALLOCX_LG_ALIGN(la) โ€” align the memory allocation to start at an address that is a multiple of (1 << la). heads up: this macro does not validate that la is within the valid range. it trusts you. be trustworthy.
  • MALLOCX_ALIGN(a) โ€” align the memory allocation to start at an address that is a multiple of a, where a is a power of two. same vibe check: this macro does not validate that a is a power of 2.
  • MALLOCX_ZERO โ€” initialize newly allocated memory to contain zero bytes. in the growing reallocation case, the real size prior to reallocation defines the boundary between untouched bytes and those that are initialized to contain zero bytes. and if this macro is absent? newly allocated memory is uninitialized. ๐Ÿ˜ญ so specify it if you want clean zeroes.
  • MALLOCX_TCACHE(tc) โ€” use the thread-specific cache (tcache) specified by the identifier tc, which must have been acquired via the tcache.create mallctl. this macro does not validate that tc specifies a valid identifier. again: trust fall.
  • MALLOCX_TCACHE_NONE โ€” do not use a thread-specific cache (tcache). unless MALLOCX_TCACHE(tc) or MALLOCX_TCACHE_NONE is specified, an automatically managed tcache will be used under many circumstances. ๐Ÿšจ this macro cannot be used in the same flags argument as MALLOCX_TCACHE(tc) โ€” they're giving enemies to lovers, pick one.
  • MALLOCX_ARENA(a) โ€” use the arena specified by the index a. this macro has no effect for regions that were allocated via an arena other than the one specified. and this macro does not validate that a specifies an arena index in the valid range.

okay now the functions themselves, in order โœจ

  • mallocx() โ€” allocates at least size bytes of memory, and returns a pointer to the base address of the allocation. behavior is undefined if size is 0. so like... don't pass 0.
  • rallocx() โ€” resizes the allocation at ptr to be at least size bytes, and returns a pointer to the base address of the resulting allocation, which may or may not have moved from its original location. behavior is undefined if size is 0.
  • xallocx() โ€” resizes the allocation at ptr in place to be at least size bytes, and returns the real size of the allocation. if extra is non-zero, an attempt is made to resize the allocation to be at least (size + extra) bytes, though inability to allocate the extra byte(s) will not by itself result in failure to resize. behavior is undefined if size is 0, or if (size + extra > SIZE_T_MAX). ๐Ÿ’€ respect the limits.
  • sallocx() โ€” returns the real size of the allocation at ptr. that's it, that's the whole function, and honestly? iconic minimalism.
  • dallocx() โ€” causes the memory referenced by ptr to be made available for future allocations. bye bestie ๐Ÿ‘‹
  • sdallocx() โ€” an extension of dallocx() with a size parameter to allow the caller to pass in the allocation size as an optimization. the minimum valid input size is the original requested size of the allocation, and the maximum valid input size is the corresponding value returned by nallocx() or sallocx(). so you gotta know your size range, iykyk.
  • nallocx() โ€” allocates no memory (yes really, zero), but it performs the same size computation as the mallocx() function, and returns the real size of the allocation that would result from the equivalent mallocx() function call, or 0 if the inputs exceed the maximum supported size class and/or alignment. behavior is undefined if size is 0. it's the "what if" of allocators, and i'm obsessed.
  • mallctl() โ€” provides a general interface for introspecting the memory allocator, as well as setting modifiable parameters and triggering actions. the period-separated name argument specifies a location in a tree-structured namespace; see the MALLCTL NAMESPACE section for documentation on the tree contents. to read a value, pass a pointer via oldp to adequate space to contain the value, and a pointer to its length via oldlenp; otherwise pass NULL and NULL. similarly, to write a value, pass a pointer to the value via newp, and its length via newlen; otherwise pass NULL and 0. it's giving "the everything function" and honestly it kinda is ๐Ÿ”ฅ
  • mallctlnametomib() โ€” provides a way to avoid repeated name lookups for applications that repeatedly query the same portion of the namespace, by translating a name to a "Management Information Base" (MIB) that can be passed repeatedly to mallctlbymib(). upon successful return from mallctlnametomib(), mibp contains an array of *miblenp integers, where *miblenp is the lesser of the number of components in name and the input value of *miblenp. thus it is possible to pass a *miblenp that is smaller than the number of period-separated name components, which results in a partial MIB that can be used as the basis for constructing a complete MIB. for name components that are integers (e.g. the 2 in arenas.bin.2.size), the corresponding MIB component will always be that integer. therefore, it is legitimate to construct code like the following:
unsigned nbins, i;
size_t mib[4];
size_t len, miblen;

len = sizeof(nbins);
mallctl("arenas.nbins", &nbins, &len, NULL, 0);

miblen = 4;
mallctlnametomib("arenas.bin.0.size", mib, &miblen);
for (i = 0; i < nbins; i++) {
     size_t bin_size;

     mib[2] = i;
     len = sizeof(bin_size);
     mallctlbymib(mib, miblen, (void *)&bin_size, &len, NULL, 0);
     /* Do something with bin_size... */
}
  • malloc_stats_print() โ€” writes summary statistics via the write_cb callback function pointer and cbopaque data passed to write_cb, or malloc_message() if write_cb is NULL. the statistics are presented in human-readable form unless "J" is specified as a character within the opts string, in which case the statistics are presented in JSON format[2] ๐ŸŽ€. this function can be called repeatedly. general information that never changes during execution can be omitted by specifying "g" as a character within the opts string.
okay so this next bit is a whole situation, stay with me: malloc_stats_print() uses the mallctl*() functions internally, so inconsistent statistics can be reported if multiple threads use these functions simultaneously. if --enable-stats is specified during configuration, "m", "d", and "a" can be specified to omit merged arena, destroyed merged arena, and per arena statistics, respectively; "b" and "l" can be specified to omit per size class statistics for bins and large objects, respectively; "x" can be specified to omit all mutex statistics; "e" can be used to omit extent statistics. unrecognized characters are silently ignored โ€” no error, no warning, they just vibe into the void. note that thread caching may prevent some statistics from being completely up to date, since extra locking would be required to merge counters that track thread cache operations. ๐Ÿ˜ญ so don't @ me about stale numbers.
  • malloc_usable_size() โ€” returns the usable size of the allocation pointed to by ptr. the return value may be larger than the size that was requested during allocation. ๐Ÿšจ important: malloc_usable_size() is not a mechanism for in-place realloc(); rather it is provided solely as a tool for introspection purposes. any discrepancy between the requested allocation size and the size reported by malloc_usable_size() should not be depended on, since such behavior is entirely implementation-dependent. so like, look but don't touch, ykwim?

๐ŸŒธ TUNING ๐ŸŒธ

once, when the first call is made to one of the memory allocation routines, the allocator initializes its internals based in part on various options that can be specified at compile- or run-time. so it's a whole "first impressions" moment for the allocator ๐Ÿ’…

the string specified via --with-malloc-conf, the string pointed to by the global variable malloc_conf, the "name" of the file referenced by the symbolic link named /etc/malloc.conf, and the value of the environment variable MALLOC_CONF, will be interpreted, in that order, from left to right as options. ๐Ÿ‘‘ order matters, bestie, don't shuffle them.

note that malloc_conf may be read before main() is entered, so the declaration of malloc_conf should specify an initializer that contains the final value to be read by jemalloc. also: --with-malloc-conf and malloc_conf are compile-time mechanisms, whereas /etc/malloc.conf and MALLOC_CONF can be safely set any time prior to program invocation. so like, different lanes, different rules ๐ŸŒˆ

an options string is a comma-separated list of option:value pairs. there is one key corresponding to each opt.* mallctl (see the MALLCTL NAMESPACE section for options documentation). for example, abort:true,narenas:1 sets the opt.abort and opt.narenas options. some options have boolean values (true/false), others have integer values (base 8, 10, or 16, depending on prefix), and yet others have raw string values. โœจ pick your fighter โœจ

๐Ÿงš Small objects, slabs, and all that jazz

Small objects are managed in groups by slabs, and honestly each slab keeps a little bitmap to track which regions are in use. It's giving clipboard energy. ๐Ÿ“‹

So the rounding rules for small requests, listen up:

  • If an allocation request is no more than half the quantum (8 or 16, depending on architecture), it gets rounded up to the nearest power of two that is at least sizeof(double).
  • All other object size classes are multiples of the quantum, spaced such that there are four size classes for each doubling in size, which limits internal fragmentation to approximately 20% for all but the smallest size classes.
  • Small size classes are smaller than four times the page size.
  • Large size classes extend from four times the page size up to the largest size class that does not exceed PTRDIFF_MAX.

๐Ÿ˜ญ the cacheline situation (bestie, read this one)

Allocations are packed tightly together, which can lowkey be an issue for multi-threaded applications. False sharing is real and it will humble you.

If you need to assure that allocations do not suffer from cacheline sharing, do one of these:

  • round your allocation requests up to the nearest multiple of the cacheline size, or
  • specify cacheline alignment when allocating.

Periodt. That's the whole fix. ๐Ÿ’…


๐Ÿซถ in-place resizing: what actually works

The realloc(), rallocx(), and xallocx() functions may resize allocations without moving them under limited circumstances. Note the limited, bestie. ๐Ÿ™ˆ

Unlike the *allocx() API, the standard API does not officially round up the usable size of an allocation to the nearest size class, so technically it is necessary to call realloc() to grow e.g. a 9-byte allocation to 16 bytes, or shrink a 16-byte allocation to 9 bytes. Yes it's silly. No we can't change it.

Growth and shrinkage trivially succeeds in place as long as the pre-size and post-size both round up to the same size class. Slay. โœจ

No other API guarantees are made regarding in-place resizing, but the current implementation also tries to resize large allocations in place, as long as the pre-size and post-size are both large:

  • For shrinkage to succeed, the extent allocator must support splitting (see arena.<i>.extent_hooks).
  • Growth only succeeds if the trailing memory is currently available, and the extent allocator supports merging.
โš ๏ธ Don't go building your whole app on "it'll probably resize in place." Only the same-size-class case is guaranteed. The rest is the allocator being nice. ๐Ÿ˜ญ

๐ŸŒธ Table 1. Size classes ๐ŸŒธ

Assuming 4 KiB pages and a 16-byte quantum on a 64-bit system, the size classes in each category are as shown in Table 1.

Category: Small

  • Spacing lg โ†’ [8]
  • Spacing 16 โ†’ [16, 32, 48, 64, 80, 96, 112, 128]
  • Spacing 32 โ†’ [160, 192, 224, 256]
  • Spacing 64 โ†’ [320, 384, 448, 512]
  • Spacing 128 โ†’ [640, 768, 896, 1024]
  • Spacing 256 โ†’ [1280, 1536, 1792, 2048]
  • Spacing 512 โ†’ [2560, 3072, 3584, 4096]
  • Spacing 1 KiB โ†’ [5 KiB, 6 KiB, 7 KiB, 8 KiB]
  • Spacing 2 KiB โ†’ [10 KiB, 12 KiB, 14 KiB]

Category: Large

  • Spacing 2 KiB โ†’ [16 KiB]
  • Spacing 4 KiB โ†’ [20 KiB, 24 KiB, 28 KiB, 32 KiB]
  • Spacing 8 KiB โ†’ [40 KiB, 48 KiB, 56 KiB, 64 KiB]
  • Spacing 16 KiB โ†’ [80 KiB, 96 KiB, 112 KiB, 128 KiB]
  • Spacing 32 KiB โ†’ [160 KiB, 192 KiB, 224 KiB, 256 KiB]
  • Spacing 64 KiB โ†’ [320 KiB, 384 KiB, 448 KiB, 512 KiB]
  • Spacing 128 KiB โ†’ [640 KiB, 768 KiB, 896 KiB, 1 MiB]
  • Spacing 256 KiB โ†’ [1280 KiB, 1536 KiB, 1792 KiB, 2 MiB]
  • Spacing 512 KiB โ†’ [2560 KiB, 3 MiB, 3584 KiB, 4 MiB]
  • Spacing 1 MiB โ†’ [5 MiB, 6 MiB, 7 MiB, 8 MiB]
  • Spacing 2 MiB โ†’ [10 MiB, 12 MiB, 14 MiB, 16 MiB]
  • Spacing 4 MiB โ†’ [20 MiB, 24 MiB, 28 MiB, 32 MiB]
  • Spacing 8 MiB โ†’ [40 MiB, 48 MiB, 56 MiB, 64 MiB]
  • Spacing ... โ†’ ...
  • Spacing 512 PiB โ†’ [2560 PiB, 3 EiB, 3584 PiB, 4 EiB]
  • Spacing 1 EiB โ†’ [5 EiB, 6 EiB, 7 EiB]

โœจ MALLCTL NAMESPACE โœจ

Okay bestie, this is the mother lode โ€” the names defined in the namespace you can reach via the mallctl*() functions. ๐Ÿ‘‘

How to read the entries, because the notation is a whole vibe:

  • Value types are specified in parentheses.
  • Readable/writable statuses are encoded as rw, r-, -w, or --.
  • Required build configuration flags follow, if any.
  • A name element encoded as <i> or <j> indicates an integer component, where the integer varies from 0 to some upper value that must be determined via introspection.

And here's the arena tea โ˜• โ€” in the case of stats.arenas.<i>.* and arena.<i>.{initialized,purge,decay,dss}, <i> equal to MALLCTL_ARENAS_ALL can be used to operate on all arenas or access the summation of statistics from all arenas. Similarly, <i> equal to MALLCTL_ARENAS_DESTROYED can be used to access the summation of statistics from all destroyed arenas.

These constants can be utilized either via mallctlnametomib() followed by mallctlbymib(), or via code such as the following:

#define STRINGIFY_HELPER(x) #x
#define STRINGIFY(x) STRINGIFY_HELPER(x)

mallctl("arena." STRINGIFY(MALLCTL_ARENAS_ALL) ".decay",
    NULL, NULL, NULL, 0);
๐Ÿšจ Take special note of the epoch mallctl, which controls refreshing of cached dynamic statistics. Like, actually take note. Stats brain rot is real and epoch is the cure. ๐Ÿ”ฅ

  • version (const char *) r- โ€” Return the jemalloc version string. That's it, that's the whole entry, and she ate. ๐Ÿ’–
  • epoch (uint64_t) rw โ€” okay so if you pass a value in, it refreshes all the data that the mallctl*() functions report from, and bumps the epoch up by one. It returns the current epoch. Why do you care? Because it's lowkey the best way to detect whether some other thread caused a refresh ๐Ÿ‘€
  • background_thread (bool) rw โ€” turns internal background worker threads on/off. Set it to true and background threads get created on demand (and no cap on vibes โ€” but fr, the number of background threads will be no more than the number of CPUs or active arenas). The threads run periodically and handle purging asynchronously, main character energy. When you switch it off, background threads are terminated synchronously. ๐Ÿ’€ Heads up though: after fork(2), the state in the child process will be disabled regardless of the state in the parent process. See stats.background_thread for the related stats. opt.background_thread can be used to set the default option. This option is only available on selected pthread-based platforms.
  • max_background_threads (size_t) rw โ€” the maximum number of background worker threads that will be created. This value is capped at opt.max_background_threads at startup. โœจ
  • config.cache_oblivious (bool) r- โ€” --enable-cache-oblivious was specified during build configuration.
  • config.debug (bool) r- โ€” --enable-debug was specified during build configuration.
  • config.fill (bool) r- โ€” --enable-fill was specified during build configuration.
  • config.lazy_lock (bool) r- โ€” --enable-lazy-lock was specified during build configuration.
  • config.malloc_conf (const char *) r- โ€” embedded configure-time-specified run-time options string ๐ŸŽ€ โ€” it's empty unless --with-malloc-conf was specified during build configuration.
  • config.prof (bool) r- โ€” --enable-prof was specified during build configuration.
  • config.prof_libgcc (bool) r- โ€” --disable-prof-libgcc was not specified during build configuration.
  • config.prof_libunwind (bool) r- โ€” --enable-prof-libunwind was specified during build configuration.
  • config.stats (bool) r- โ€” --enable-stats was specified during build configuration.
  • config.utrace (bool) r- โ€” --enable-utrace was specified during build configuration.
  • config.xmalloc (bool) r- โ€” --enable-xmalloc was specified during build configuration.

the opt.* girlies ๐ŸŒธ

  • opt.abort (bool) r- โ€” abort-on-warning, enabled/disabled. If true, most warnings are fatal ๐Ÿ˜ญ. Note that runtime option warnings are not included (see opt.abort_conf for that one). The process will call abort(3) in these cases. This option is disabled by default โ€” unless --enable-debug is specified during configuration, in which case it is enabled by default.
  • opt.confirm_conf (bool) r- โ€” confirm-runtime-options-when-program-starts, enabled/disabled. If true, the string specified via --with-malloc-conf, the string pointed to by the global variable malloc_conf, the โ€œnameโ€ of the file referenced by the symbolic link named /etc/malloc.conf, and the value of the environment variable MALLOC_CONF, will be printed in order. Then, each option being set will be individually printed. This option is disabled by default. ๐Ÿซถ
  • opt.abort_conf (bool) r- โ€” abort-on-invalid-configuration, enabled/disabled. If true, invalid runtime options are fatal ๐Ÿ’€. The process will call abort(3) in these cases. This option is disabled by default โ€” unless --enable-debug is specified during configuration, in which case it is enabled by default.
  • opt.cache_oblivious (bool) r- โ€” enable/disable cache-oblivious large allocation alignment, for large requests with no alignment constraints. If this feature is disabled, all large allocations are page-aligned as an implementation artifact, which can severely harm CPU cache utilization ๐Ÿ˜ฌ. But the cache-oblivious layout comes at the cost of one extra page per large allocation, which in the most extreme case increases physical memory usage for the 16 KiB size class to 20 KiB. This option is enabled by default.
  • opt.metadata_thp (const char *) r- โ€” controls whether jemalloc is allowed to use transparent huge page (THP) for internal metadata (see stats.metadata). "always" allows such usage. "auto" uses no THP initially, but may begin to do so when metadata usage reaches a certain level. The default is "disabled".
  • opt.trust_madvise (bool) r- โ€” if true, do not perform the runtime check for MADV_DONTNEED, which checks that it actually zeros pages. The default is disabled on Linux and enabled elsewhere. ๐Ÿง
  • opt.retain (bool) r- โ€” if true, retain unused virtual memory for later reuse rather than discarding it by calling munmap(2) or equivalent (see stats.retained for related details). It also makes jemalloc use mmap(2) or equivalent in a more greedy way, mapping larger chunks in one go. ๐Ÿ‘‘

This option is disabled by default unless discarding virtual memory is known to trigger platform-specific performance problems, namely:

  1. for [64-bit] Linux, which has a quirk in its virtual memory allocation algorithm that causes semi-permanent VM map holes under normal jemalloc operation; and
  2. for [64-bit] Windows, which disallows split / merged regions with MEM_RELEASE.

Although the same issues may present on 32-bit platforms as well, retaining virtual memory for 32-bit Linux and Windows is disabled by default due to the practical possibility of address space exhaustion. ๐Ÿ˜ญ

  • opt.dss (const char *) r- โ€” dss (sbrk(2)) allocation precedence as related to mmap(2) allocation. The following settings are supported if sbrk(2) is supported by the operating system: "disabled", "primary", and "secondary"; otherwise only "disabled" is supported. The default is "secondary" if sbrk(2) is supported by the operating system; "disabled" otherwise.
  • opt.narenas (unsigned) r- โ€” maximum number of arenas to use for automatic multiplexing of threads and arenas. The default is four times the number of CPUs, or one if there is a single CPU. ๐Ÿงฎ
  • opt.oversize_threshold (size_t) r- โ€” the threshold in bytes above which requests are considered oversize. Allocation requests with greater sizes are fulfilled from a dedicated arena (automatically managed, however not within narenas), in order to reduce fragmentation by not mixing huge allocations with small ones. In addition, the decay API guarantees on the extents greater than the specified threshold may be overridden. Note that requests with arena index specified via MALLOCX_ARENA, or threads associated with explicit arenas, will not be considered. The default threshold is 8MiB. Values not within large size classes disable this feature. ๐Ÿ”ฅ
  • opt.percpu_arena (const char *) r- โ€” per CPU arena mode. Use the "percpu" setting to enable this feature, which uses the number of CPUs to determine the number of arenas, and binds threads to arenas dynamically based on the CPU the thread runs on currently. The "phycpu" setting uses one arena per physical CPU, which means the two hyper threads on the same CPU share one arena. Note that no runtime checking regarding the availability of hyper threading is done at the moment ๐Ÿ‘€. When set to "disabled", narenas and thread-to-arena association will not be impacted by this option. The default is "disabled".
  • opt.background_thread (bool) r- โ€” internal background worker threads, enabled/disabled. Because of potential circular dependencies, enabling background thread using this option may cause a crash or deadlock during initialization ๐Ÿ’€. For a reliable way to use this feature, see background_thread for the dynamic control options and details. This option is disabled by default.
  • opt.max_background_threads (size_t) r- โ€” maximum number of background threads that will be created if background_thread is set. Defaults to number of cpus.
  • opt.dirty_decay_ms (ssize_t) r- โ€” approximate time in milliseconds from the creation of a set of unused dirty pages until an equivalent set of unused dirty pages is purged (i.e. converted to muzzy via e.g. madvise(...MADV_FREE) if supported by the operating system, or converted to clean otherwise) and/or reused. Dirty pages are defined as previously having been potentially written to by the application, and therefore consuming physical memory, yet having no current use. The pages are incrementally purged according to a sigmoidal decay curve that starts and ends with zero purge rate ๐ŸŒ€. A decay time of 0 causes all unused dirty pages to be purged immediately upon creation. A decay time of -1 disables purging. The default decay time is 10 seconds. See arenas.dirty_decay_ms and arena.<i>.dirty_decay_ms for related dynamic control options. See opt.muzzy_decay_ms for a description of muzzy pages. Note that when the oversize_threshold feature is enabled, the arenas reserved for oversize requests may have their own default decay settings.
  • opt.muzzy_decay_ms (ssize_t) r- โ€” approximate time in milliseconds from the creation of a set of unused muzzy pages until an equivalent set of unused muzzy pages is purged (i.e. converted to clean) and/or reused. Muzzy pages are defined as previously having been unused dirty pages that were subsequently purged in a manner that left them subject to the reclamation whims of the operating system (e.g. madvise(...MADV_FREE)), and therefore in an indeterminate state ๐Ÿฆ‹. The pages are incrementally purged according to a sigmoidal decay curve that starts and ends with zero purge rate. A decay time of 0 causes all unused muzzy pages to be purged immediately upon creation. A decay time of -1 disables purging. The default decay time is 10 seconds. See arenas.muzzy_decay_ms and arena.<i>.muzzy_decay_ms for related dynamic control options.
  • opt.lg_extent_max_active_fit (size_t) r- โ€” when reusing dirty extents, this determines the (log base 2 of the) maximum ratio between the size of the active extent selected (to split off from) and the size of the requested allocation. This prevents the splitting of large active extents for smaller allocations, which can reduce fragmentation over the long run (especially for non-active extents) โœจ. Lower value may reduce fragmentation, at the cost of extra active extents. The default value is 6, which gives a maximum ratio of 64 (2^6).
  • opt.stats_print (bool) r- โ€” turns stats printing at exit on/off. If it's on, malloc_stats_print() gets called at program exit through an atexit(3) function, and you can pair it with opt.stats_print_opts to pick the output options. โš ๏ธ big warning tho: if --enable-stats was specified during configuration, this has the potential to cause deadlock for a multi-threaded process that exits while one or more threads are executing in the memory allocation functions. Also, atexit() may allocate memory during application initialization and then deadlock internally when jemalloc in turn calls atexit(), so this option is not universally usable (tho the application can register its own atexit() function with equivalent functionality, so like, there's a way out). So use it with care, bestie โ€” it's primarily intended as a performance tuning aid during application development. Disabled by default. ๐Ÿ’€
ngl this one is giving "works on my machine" energy โ€” read the caveat twice before you ship it.
  • opt.stats_print_opts (const char *) r- โ€” the options (the opts string) to pass to the malloc_stats_print() at exit (enabled through opt.stats_print). See available options in malloc_stats_print(). Has no effect unless opt.stats_print is enabled. Default is "". ๐ŸŽ€
  • opt.stats_interval (int64_t) r- โ€” average interval between statistics outputs, measured in bytes of allocation activity. The actual interval may be sporadic because decentralized event counters are used to avoid synchronization bottlenecks (so don't expect it to be all precise and cute). The output may be triggered on any thread, which then calls malloc_stats_print(). You can combine it with opt.stats_interval_opts to specify output options. By default, interval-triggered stats output is disabled (encoded as -1). โœจ
  • opt.stats_interval_opts (const char *) r- โ€” the options (the opts string) to pass to malloc_stats_print() for interval based statistics printing (enabled through opt.stats_interval). See available options in malloc_stats_print(). Has no effect unless opt.stats_interval is enabled. Default is "". ๐Ÿซถ
  • opt.junk (const char *) r- [--enable-fill] โ€” junk filling, aka the "make bugs scream" option. The values:
    • "alloc" โ€” each byte of uninitialized allocated memory will be initialized to 0xa5.
    • "free" โ€” all deallocated memory will be initialized to 0x5a.
    • "true" โ€” both allocated and deallocated memory will be initialized.
    • "false" โ€” junk filling be disabled entirely.

This is intended for debugging and will impact performance negatively (obviously, it's touching every byte, she's doing the most). Default is "false" unless --enable-debug is specified during configuration, in which case it is "true" by default. ๐Ÿ”ฅ

  • opt.zero (bool) r- [--enable-fill] โ€” zero filling enabled/disabled. If enabled, each byte of uninitialized allocated memory will be initialized to 0. Note that this initialization only happens once for each byte, so realloc() and rallocx() calls do not zero memory that was previously allocated โ€” don't come for her when your recycled bytes are still there ๐Ÿ™ˆ. Intended for debugging, impacts performance negatively. Disabled by default.
  • opt.utrace (bool) r- [--enable-utrace] โ€” allocation tracing based on utrace(2) enabled/disabled. Disabled by default.
  • opt.xmalloc (bool) r- [--enable-xmalloc] โ€” abort-on-out-of-memory enabled/disabled. If enabled, rather than returning failure for any allocation function, display a diagnostic message on STDERR_FILENO and cause the program to drop core (using abort(3)). If an application is designed to depend on this behavior, set the option at compile time by including the following in the source code:
malloc_conf = "xmalloc:true";

This option is disabled by default. ๐Ÿ’…

  • opt.tcache (bool) r- โ€” thread-specific caching (tcache) enabled/disabled. When there are multiple threads, each thread uses a tcache for objects up to a certain size. Thread-specific caching allows many allocations to be satisfied without performing any thread synchronization, at the cost of increased memory use. See the opt.tcache_max option for related tuning information. Enabled by default โ€” this is the main character of default perf settings fr fr. ๐Ÿ‘‘
  • opt.tcache_max (size_t) r- โ€” maximum size class to cache in the thread-specific cache (tcache). At a minimum, the first size class is cached; and at a maximum, size classes up to 8 MiB can be cached. The default maximum is 32 KiB (2^15). As a convenience, this may also be set by specifying lg_tcache_max, which will be taken to be the base-2 logarithm of the setting of tcache_max. ๐ŸŒธ
  • opt.thp (const char *) r- โ€” transparent hugepage (THP) mode. Settings "always", "never" and "default" are available if THP is supported by the operating system.
    • "always" enables transparent hugepage for all user memory mappings with MADV_HUGEPAGE.
    • "never" ensures no transparent hugepage with MADV_NOHUGEPAGE.
    • "default" (the default setting, iconically) makes no changes.

Note that: this option does not affect THP for jemalloc internal metadata (see opt.metadata_thp); in addition, for arenas with customized extent_hooks, this option is bypassed as it is implemented as part of the default extent hooks. ๐Ÿฆ‹

  • opt.prof (bool) r- [--enable-prof] โ€” memory profiling enabled/disabled. If enabled, profile memory allocation activity. See the opt.prof_active option for on-the-fly activation/deactivation. See the opt.lg_prof_sample option for probabilistic sampling control. See the opt.prof_accum option for control of cumulative sample reporting. See the opt.lg_prof_interval option for information on interval-triggered profile dumping, the opt.prof_gdump option for information on high-water-triggered profile dumping, and the opt.prof_final option for final profile dumping. Profile output is compatible with the jeprof command, which is based on the pprof that is developed as part of the gperftools package[3]. See HEAP PROFILE FORMAT for heap profile format documentation. โœจ
  • opt.prof_prefix (const char *) r- [--enable-prof] โ€” filename prefix for profile dumps. If the prefix is set to the empty string, no automatic dumps will occur; this is primarily useful for disabling the automatic final heap dump (which also disables leak reporting, if enabled). The default prefix is jeprof. This prefix value can be overridden by prof.prefix. ๐ŸŽ€
  • opt.prof_active (bool) r- [--enable-prof] โ€” profiling activated/deactivated. This is a secondary control mechanism that makes it possible to start the application with profiling enabled (see the opt.prof option) but inactive, then toggle profiling at any time during program execution with the prof.active mallctl. Enabled by default.
  • opt.prof_thread_active_init (bool) r- [--enable-prof] โ€” initial setting for thread.prof.active in newly created threads. The initial setting for newly created threads can also be changed during execution via the prof.thread_active_init mallctl. Enabled by default. ๐Ÿงš
  • opt.lg_prof_sample (size_t) r- [--enable-prof] โ€” average interval (log base 2) between allocation samples, measured in bytes of allocation activity. Increasing the sampling interval decreases profile fidelity, but also decreases the computational overhead โ€” the classic tradeoff, no cap. The default sample interval is 512 KiB (2^19 B). โญ
  • opt.prof_accum (bool) r- [--enable-prof] โ€” reporting of cumulative object/byte counts in profile dumps enabled/disabled. If this option is enabled, every unique backtrace must be stored for the duration of execution. Depending on the application, this can impose a large memory overhead, and the cumulative counts are not always of interest. Disabled by default โ€” she's expensive and lowkey not always worth it. ๐Ÿ’œ
  • opt.lg_prof_interval (ssize_t) r- [--enable-prof] โ€” average interval (log base 2) between memory profile dumps, as measured in bytes of allocation activity. The actual interval between dumps may be sporadic because decentralized allocation counters are used to avoid synchronization bottlenecks. Profiles are dumped to files named according to the pattern <prefix>.<pid>.<seq>.i<iseq>.heap, where <prefix> is controlled by the opt.prof_prefix and prof.prefix options. By default, interval-triggered profile dumping is disabled (encoded as -1). ๐Ÿ“
  • opt.prof_gdump (bool) r- [--enable-prof] โ€” set the initial state of prof.gdump, which when enabled triggers a memory profile dump every time the total virtual memory exceeds the previous maximum. Disabled by default. ๐Ÿ“ˆ
  • opt.prof_final (bool) r- [--enable-prof] โ€” use an atexit(3) function to dump final memory usage to a file named according to the pattern <prefix>.<pid>.<seq>.f.heap, where <prefix> is controlled by the opt.prof_prefix and prof.prefix options. Note that atexit() may allocate memory during application initialization and then deadlock internally when jemalloc in turn calls atexit(), so this option is not universally usable (though the application can register its own atexit() function with equivalent functionality). Disabled by default. ๐Ÿ˜ญ
  • opt.prof_leak (bool) r- [--enable-prof] โ€” leak reporting enabled/disabled. If enabled, use an atexit(3) function to report memory leaks detected by allocation sampling. See the opt.prof option for information on analyzing heap profile output. Works only when combined with opt.prof_final, otherwise does nothing (so like, pair it up or it's just vibing doing nothing). Disabled by default. ๐Ÿ™ˆ
  • opt.prof_leak_error (bool) r- [--enable-prof] โ€” similar to opt.prof_leak, but makes the process exit with error code 1 if a memory leak is detected. This option supersedes opt.prof_leak, meaning that if both are specified, this option takes precedence. When enabled, also enables opt.prof_leak. Works only when combined with opt.prof_final, otherwise does nothing. Disabled by default. ๐Ÿ’€
  • opt.zero_realloc (const char *) r- โ€” determines the behavior of realloc() when passed a value of zero for the new size.
    • "alloc" treats this as an allocation of size zero (and returns a non-null result except in case of resource exhaustion).
    • "free" treats this as a deallocation of the pointer, and returns NULL without setting errno.
    • "abort" aborts the process if zero is passed.

The default is "free" on Linux and Windows, and "alloc" elsewhere. ๐ŸŒˆ

So yeah โ€” there's considerable divergence across implementations on how this case gets handled. A lot of them just do the "free" thing. And like... that can literally introduce security vulnerabilities, because a NULL return value means failure, and the passed-in pointer staying valid is what POSIX and C11 say should happen. The "alloc" option is safe, but it can cause leaks in programs that expect the common behavior. So real talk: programs that wanna be portable and leak-free cannot assume either behavior, and therefore must never call realloc with a size of 0. The "abort" option exists so you can test this behavior. ๐Ÿ’€


  • thread.arena (unsigned) rw โ€” get or set the arena that's attached to the calling thread. If the arena you specified wasn't initialized beforehand (peep the arena.i.initialized mallctl), it'll get auto-initialized as a side effect of calling this. โœจ
  • thread.allocated (uint64_t) r- [--enable-stats] โ€” get the total number of bytes ever allocated by the calling thread. Heads up though, this counter can wrap around, and it's on the application to interpret it correctly when that happens. ๐ŸŒ€
  • thread.allocatedp (uint64_t *) r- [--enable-stats] โ€” get a pointer to the value returned by the thread.allocated mallctl. Iconic for skipping the overhead of calling mallctl*() over and over. Just don't go modifying the underlying counter, bestie โ€” that's not yours. ๐Ÿ™ˆ
  • thread.deallocated (uint64_t) r- [--enable-stats] โ€” get the total number of bytes ever deallocated by the calling thread. Same wrap-around situation as above: it can wrap, and it's up to the application to interpret it appropriately. ๐ŸŒช๏ธ
  • thread.deallocatedp (uint64_t *) r- [--enable-stats] โ€” get a pointer to the value returned by the thread.deallocated mallctl. Again: avoids the overhead of repeated mallctl*() calls. Again: the underlying counter should not be modified by the application. ๐Ÿซถ
  • thread.peak.read (uint64_t) r- [--enable-stats] โ€” get an approximation of the max value of (bytes allocated โˆ’ bytes deallocated) by the calling thread, since the last thread.peak.reset call, or since the thread was created if it never called thread.peak.reset. They make zero guarantees about how good the approximation is, but jemalloc currently tries to keep it accurate to within one hundred kilobytes. ๐Ÿ“
  • thread.peak.reset (void) -- [--enable-stats] โ€” resets the net-bytes-allocated counter for the calling thread to zero. This affects later thread.peak.read calls, but not the values you get from thread.allocated or thread.deallocated. ๐ŸŽฏ
  • thread.tcache.enabled (bool) rw โ€” enable/disable the calling thread's tcache. The tcache gets implicitly flushed as a side effect of becoming disabled (see thread.tcache.flush). ๐Ÿงบ
  • thread.tcache.flush (void) -- โ€” flush the calling thread's thread-specific cache (tcache). This releases all cached objects and internal data structures tied to the calling thread's tcache. Normally you don't need to call this at all, since automatic periodic incremental garbage collection happens on its own, and the thread cache gets automatically discarded when a thread exits. But โ€” and this is the lowkey important part โ€” garbage collection is triggered by allocation activity, so a thread that stops allocating/deallocating can hold onto its cache indefinitely. In that case, manual flushing might actually be useful to you. ๐Ÿงน
  • thread.prof.name (const char ) r- or -w [--enable-prof] โ€” get/set the descriptive name associated with the calling thread in memory profile dumps. An internal copy of the name string gets created, so you don't have to maintain your input string after this interface finishes. For the output string? Copy it if you need it to stick around, because multiple implementation details can cause asynchronous string deallocation. Also, each invocation can only read *or write โ€” simultaneous read/write is not supported due to string lifetime limitations. The name string must be nil-terminated and made up only of characters from the sets recognized by isgraph(3) and isblank(3). ๐Ÿท๏ธ
  • thread.prof.active (bool) rw [--enable-prof] โ€” controls whether sampling is currently active for the calling thread. It's an activation mechanism on top of prof.active; both have to be active for the calling thread to sample. Enabled by default. ๐Ÿ”ฅ
  • thread.idle (void) -- โ€” hints to jemalloc that the calling thread is gonna be idle for some nontrivial period of time (like, on the order of seconds), and that doing some cleanup ops might be a good move. Zero guarantees about which specific operations happen; currently it flushes the caller's tcache and may (according to some heuristic) purge its associated arena.

Important vibe check: this is not meant to be a general-purpose background activity mechanism, and threads should not wake up multiple times just to call it. Instead โ€” a thread waiting for a task should do a timed wait first, call thread.idle if no task shows up in the timeout interval, and then do an untimed wait. If you want an actual background activity mechanism, see background_thread. ๐Ÿ˜ด

  • tcache.create (unsigned) r- โ€” create an explicit thread-specific cache (tcache) and return an identifier you can pass to the MALLOCX_TCACHE(tc) macro, so you explicitly use that cache instead of the automatically managed one that's used by default. Each explicit cache can only be used by one thread at a time, and the application has to make sure that constraint actually holds. ๐Ÿค

Also: if the amount of space you supplied for storing the thread-specific cache identifier doesn't equal sizeof(unsigned), then no thread-specific cache will be created, no data will be written to the space pointed to by oldp, and *oldlenp will be set to 0.

  • tcache.flush (unsigned) -w โ€” flush the specified thread-specific cache (tcache). Same considerations apply here as for thread.tcache.flush, except that the tcache will never be automatically discarded. ๐Ÿšฟ
  • tcache.destroy (unsigned) -w โ€” flush the specified thread-specific cache (tcache) and make the identifier available for use during a future tcache creation. โ™ป๏ธ
  • arena.<i>.initialized (bool) r- โ€” get whether the specified arena's statistics are initialized (i.e. the arena was initialized prior to the current epoch). You can also nominally use this to query whether the merged statistics corresponding to MALLCTL_ARENAS_ALL are initialized โ€” which is always true. โœ…
  • arena.<i>.decay (void) -- โ€” trigger decay-based purging of unused dirty/muzzy pages for arena <i>, or for all arenas if <i> equals MALLCTL_ARENAS_ALL. How much of the unused dirty/muzzy pages gets purged depends on the current time; see opt.dirty_decay_ms and opt.muzy_decay_ms for the deets. โณ
  • arena.<i>.purge (void) -- โ€” purge all unused dirty pages for arena <i>, or for all arenas if <i> equals MALLCTL_ARENAS_ALL. ๐Ÿงฝ
  • arena.<i>.reset (void) -- โ€” discard all of the arena's extant allocations. Only usable with arenas explicitly created via arenas.create. After this, none of the arena's discarded/cached allocations may be accessed โ€” and as part of that requirement, all thread caches that were used to allocate/deallocate in conjunction with the arena must be flushed beforehand. ๐Ÿ˜ฌ
  • arena.<i>.destroy (void) -- โ€” destroy the arena. It discards all of the arena's extant allocations using the same mechanism as arena.<i>.reset (with all the same constraints and side effects), merges the arena stats into the ones accessible at arena index MALLCTL_ARENAS_DESTROYED, and then completely discards all metadata associated with the arena. Future arenas.create calls may recycle that arena index. Destruction will fail if any threads are currently associated with the arena as a result of thread.arena calls. ๐Ÿ’€
  • arena.<i>.dss (const char *) rw โ€” set the precedence of dss allocation as it relates to mmap allocation for arena <i>, or for all arenas if <i> equals MALLCTL_ARENAS_ALL. See opt.dss for the supported settings. ๐Ÿ—‚๏ธ
  • arena.<i>.dirty_decay_ms (ssize_t) rw โ€” the current per-arena approximate time in milliseconds from the creation of a set of unused dirty pages until an equivalent set of unused dirty pages gets purged and/or reused. Every time you set this, all currently unused dirty pages are considered to have fully decayed โ€” which causes immediate purging of all unused dirty pages unless the decay time is set to -1 (i.e. purging disabled). See opt.dirty_decay_ms for more. โฑ๏ธ
  • arena.<i>.muzzy_decay_ms (ssize_t) rw โ€” the current per-arena approximate time in milliseconds from the creation of a set of unused muzzy pages until an equivalent set of unused muzzy pages gets purged and/or reused. Every time you set this, all currently unused muzzy pages are considered to have fully decayed โ€” causing immediate purging of all unused muzzy pages unless the decay time is set to -1 (i.e. purging disabled). See opt.muzzy_decay_ms for more. ๐ŸŒซ๏ธ
  • arena.<i>.retain_grow_limit (size_t) rw โ€” the maximum size to grow the retained region (only relevant when opt.retain is enabled). This controls the maximum increment for expanding virtual memory, or allocation through arena.<i>extent_hooks. In particular, if custom extent hooks reserve physical memory (like, say, 1G huge pages), this is genuinely useful for controlling the allocation hook's input size. Default is no limit. ๐ŸŒฑ
  • arena.<i>.extent_hooks (extent_hooks_t ) rw โ€” get or set the extent management hook functions for arena <i>. The functions must be able to operate on all extant extents associated with arena <i>, usually by passing unknown extents to the replaced functions. In practice, you can feasibly control allocation for arenas explicitly created via arenas.create so that all extents originate from an application-supplied extent allocator (by specifying the custom extent hook functions during arena creation). However โ€” the API guarantees for automatically created arenas may be relaxed; hooks set there may be called in a "best effort" fashion, and on top of that, there may be extents created *before the application even got a chance to take over extent allocation. ๐Ÿช
typedef extent_hooks_s extent_hooks_t;
struct extent_hooks_s {
     extent_alloc_t      *alloc;
     extent_dalloc_t          *dalloc;
     extent_destroy_t    *destroy;
     extent_commit_t          *commit;
     extent_decommit_t   *decommit;
     extent_purge_t      *purge_lazy;
     extent_purge_t      *purge_forced;
     extent_split_t      *split;
     extent_merge_t      *merge;
};

okay so thatโ€™s the whole function-pointer squad ๐Ÿ’… the extent_hooks_t structure is made of function pointers, and every single one gets its own lil spotlight below. jemalloc uses these functions to manage extent lifetime โ€” it starts with allocation of mapped committed memory, and in the simplest case thatโ€™s followed by deallocation. but bestie, there are performance and platform reasons to retain extents for later reuse, so itโ€™s not always that simple. cleanup attempts cascade from deallocation to decommit to forced purging to lazy purging, which gives the extent management functions opportunities to reject the most permanent cleanup operations in favor of less permanent (and often less costly) operations. all operations except allocation can be universally opted out of by setting the hook pointers to NULL, or selectively opted out of by returning failure. and note โ€” once the extent hook is set, the structure is accessed directly by the associated arenas, so it must remain valid for the entire lifetime of the arenas. no cap.

typedef void *(extent_alloc_t)(extent_hooks_t *extent_hooks,
                               void *new_addr, size_t size,
                               size_t alignment, bool *zero,
                               bool *commit, unsigned arena_ind);

an extent allocation function conforms to the extent_alloc_t type, and upon success it returns a pointer to size bytes of mapped memory on behalf of arena arena_ind such that the extentโ€™s base address is a multiple of alignment. it also sets *zero to indicate whether the extent is zeroed, and *commit to indicate whether the extent is committed. upon error the function returns NULL and leaves *zero and *commit unmodified. the size parameter is always a multiple of the page size. the alignment parameter is always a power of two at least as large as the page size. zeroing is mandatory if *zero is true upon function entry. committing is mandatory if *commit is true upon function entry. if new_addr is not NULL, the returned pointer must be new_addr on success or NULL on error. committed memory may be committed in absolute terms as on a system that does not overcommit, or in implicit terms as on a system that overcommits and satisfies physical memory needs on demand via soft page faults. note that replacing the default extent allocation function makes the arenaโ€™s arena.<i>.dss setting irrelevant ๐Ÿซถ

typedef bool (extent_dalloc_t)(extent_hooks_t *extent_hooks,
                               void *addr, size_t size,
                               bool committed, unsigned arena_ind);

an extent deallocation function conforms to the extent_dalloc_t type and deallocates an extent at given addr and size with committed/decommited memory as indicated, on behalf of arena arena_ind, returning false upon success. if the function returns true, this indicates opt-out from deallocation; the virtual memory mapping associated with the extent remains mapped, in the same commit state, and available for future use, in which case it will be automatically retained for later reuse โœจ

typedef void (extent_destroy_t)(extent_hooks_t *extent_hooks,
                                void *addr, size_t size,
                                bool committed, unsigned arena_ind);

an extent destruction function conforms to the extent_destroy_t type and unconditionally destroys an extent at given addr and size with committed/decommited memory as indicated, on behalf of arena arena_ind. this function may be called to destroy retained extents during arena destruction (see arena.<i>.destroy). like, itโ€™s the no-take-backs option ๐Ÿ’€

typedef bool (extent_commit_t)(extent_hooks_t *extent_hooks,
                               void *addr, size_t size,
                               size_t offset, size_t length,
                               unsigned arena_ind);

an extent commit function conforms to the extent_commit_t type and commits zeroed physical memory to back pages within an extent at given addr and size at offset bytes, extending for length on behalf of arena arena_ind, returning false upon success. committed memory may be committed in absolute terms as on a system that does not overcommit, or in implicit terms as on a system that overcommits and satisfies physical memory needs on demand via soft page faults. if the function returns true, this indicates insufficient physical memory to satisfy the request ๐Ÿ˜ญ

typedef bool (extent_decommit_t)(extent_hooks_t *extent_hooks,
                                 void *addr, size_t size,
                                 size_t offset, size_t length,
                                 unsigned arena_ind);

an extent decommit function conforms to the extent_decommit_t type and decommits any physical memory that is backing pages within an extent at given addr and size at offset bytes, extending for length on behalf of arena arena_ind, returning false upon success, in which case the pages will be committed via the extent commit function before being reused. if the function returns true, this indicates opt-out from decommit; the memory remains committed and available for future use, in which case it will be automatically retained for later reuse ๐Ÿงš

typedef bool (extent_purge_t)(extent_hooks_t *extent_hooks,
                              void *addr, size_t size,
                              size_t offset, size_t length,
                              unsigned arena_ind);

an extent purge function conforms to the extent_purge_t type and discards physical pages within the virtual memory mapping associated with an extent at given addr and size at offset bytes, extending for length on behalf of arena arena_ind. a lazy extent purge function (e.g. implemented via madvise(...MADV_FREE)) can delay purging indefinitely and leave the pages within the purged virtual memory range in an indeterminite state, whereas a forced extent purge function immediately purges, and the pages within the virtual memory range will be zero-filled the next time they are accessed. if the function returns true, this indicates failure to purge. forced purge says โ€œnowโ€, lazy purge says โ€œwhenever, bestieโ€ ๐ŸŒธ

typedef bool (extent_split_t)(extent_hooks_t *extent_hooks,
                              void *addr, size_t size,
                              size_t size_a, size_t size_b,
                              bool committed, unsigned arena_ind);

an extent split function conforms to the extent_split_t type and optionally splits an extent at given addr and size into two adjacent extents, the first of size_a bytes, and the second of size_b bytes, operating on committed/decommitted memory as indicated, on behalf of arena arena_ind, returning false upon success. if the function returns true, this indicates that the extent remains unsplit and therefore should continue to be operated on as a whole ๐ŸŽ€

typedef bool (extent_merge_t)(extent_hooks_t *extent_hooks,
                              void *addr_a, size_t size_a,
                              void *addr_b, size_t size_b,
                              bool committed, unsigned arena_ind);

an extent merge function conforms to the extent_merge_t type and optionally merges adjacent extents, at given addr_a and size_a with given addr_b and size_b into one contiguous extent, operating on committed/decommitted memory as indicated, on behalf of arena arena_ind, returning false upon success. if the function returns true, this indicates that the extents remain distinct mappings and therefore should continue to be operated on independently ๐Ÿ’…

  • arenas.narenas (unsigned) r- โ€” Current limit on number of arenas.
  • arenas.dirty_decay_ms (ssize_t) rw โ€” current default per-arena approximate time in milliseconds from the creation of a set of unused dirty pages until an equivalent set of unused dirty pages is purged and/or reused, used to initialize arena.<i>.dirty_decay_ms during arena creation. see opt.dirty_decay_ms for additional information.
  • arenas.muzzy_decay_ms (ssize_t) rw โ€” current default per-arena approximate time in milliseconds from the creation of a set of unused muzzy pages until an equivalent set of unused muzzy pages is purged and/or reused, used to initialize arena.<i>.muzzy_decay_ms during arena creation. see opt.muzzy_decay_ms for additional information.
  • arenas.quantum (size_t) r- โ€” Quantum size.
  • arenas.page (size_t) r- โ€” Page size.
  • arenas.tcache_max (size_t) r- โ€” Maximum thread-cached size class.
  • arenas.nbins (unsigned) r- โ€” number of bin size classes.
  • arenas.nhbins (unsigned) r- โ€” total number of thread cache bin size classes.
  • arenas.bin.<i>.size (size_t) r- โ€” maximum size supported by size class.
  • arenas.bin.<i>.nregs (uint32_t) r- โ€” number of regions per slab.
  • arenas.bin.<i>.slab_size (size_t) r- โ€” number of bytes per slab.
  • arenas.nlextents (unsigned) r- โ€” total number of large size classes.
  • arenas.lextent.<i>.size (size_t) r- โ€” maximum size supported by this large size class.
  • arenas.create (unsigned, extent_hooks_t *) rw โ€” explicitly create a new arena outside the range of automatically managed arenas, with optionally specified extent hooks, and return the new arena index. if the amount of space supplied for storing the arena index does not equal sizeof(unsigned), no arena will be created, no data will be written to the space pointed by oldp, and *oldlenp will be set to 0.
  • arenas.lookup (unsigned, void*) rw โ€” index of the arena to which an allocation belongs to.
  • prof.thread_active_init (bool) rw [--enable-prof] โ€” control the initial setting for thread.prof.active in newly created threads. see the opt.prof_thread_active_init option for additional information.
  • prof.active (bool) rw [--enable-prof] โ€” control whether sampling is currently active. see the opt.prof_active option for additional information, as well as the interrelated thread.prof.active mallctl.
  • prof.dump (const char *) -w [--enable-prof] โ€” dumps a memory profile into the file you name. If you pass NULL bestie, it makes its own filename following the pattern <prefix>.<pid>.<seq>.m<mseq>.heap, where <prefix> comes from the opt.prof_prefix and prof.prefix options. iconic, it names itself โœจ
  • prof.prefix (const char *) -w [--enable-prof] โ€” sets the filename prefix for profile dumps. Peep opt.prof_prefix for the default, k? Super handy for telling dumps apart, like when you've got forked processes running around ๐Ÿซถ
  • prof.gdump (bool) rw [--enable-prof] โ€” turn this on and every time total virtual memory passes the previous max, a memory profile dump gets triggered. Dumps land in files named <prefix>.<pid>.<seq>.u<useq>.heap, with <prefix> from opt.prof_prefix and prof.prefix. it's giving "auto-screenshotting your own memory" ๐Ÿ“ธ
  • prof.reset (size_t) -w [--enable-prof] โ€” nukes all memory profile statistics back to zero, and optionally updates the sample rate (see opt.lg_prof_sample and prof.lg_sample). fresh start energy ๐ŸŒธ
  • prof.lg_sample (size_t) r- [--enable-prof] โ€” reads back the current sample rate (see opt.lg_prof_sample).
  • prof.interval (uint64_t) r- [--enable-prof] โ€” the average number of bytes allocated between interval-based profile dumps. opt.lg_prof_interval has the extra deets if you want them ๐Ÿ’œ

๐Ÿ’– stats, the whole little family ๐Ÿ’–

okay so this is the part where you can just look at everything. reading only, no touching, we love a look-don't-touch moment ๐Ÿ‘‘

  • stats.allocated (size_t) r- [--enable-stats] โ€” total bytes your application has allocated.
  • stats.active (size_t) r- [--enable-stats] โ€” total bytes in active pages allocated by your application. It's a multiple of the page size, and it's greater than or equal to stats.allocated. It does not include stats.arenas.<i>.pdirty, stats.arenas.<i>.pmuzzy, or pages entirely devoted to allocator metadata. read that again, it's picky on purpose ๐Ÿ’…
  • stats.metadata (size_t) r- [--enable-stats] โ€” total bytes dedicated to metadata, which is made of base allocations used for bootstrap-sensitive allocator metadata structures (see stats.arenas.<i>.base) plus internal allocations (see stats.arenas.<i>.internal). Transparent huge page usage (enabled with opt.metadata_thp) is not counted here. don't come for it, that's just the rules ๐Ÿฆ‹
  • stats.metadata_thp (size_t) r- [--enable-stats] โ€” how many transparent huge pages (THP) are being used for metadata. See stats.metadata and opt.metadata_thp for the details ๐Ÿ‘€
  • stats.resident (size_t) r- [--enable-stats] โ€” the maximum number of bytes in physically resident data pages mapped by the allocator, comprising all pages dedicated to allocator metadata, pages backing active allocations, and unused dirty pages. Key word: maximum, not precise โ€” because pages might not actually be physically resident if they're demand-zeroed virtual memory nobody's touched yet. It's a multiple of the page size and it's larger than stats.active. it's giving "theoretical roof" ๐Ÿ 
  • stats.mapped (size_t) r- [--enable-stats] โ€” total bytes in active extents mapped by the allocator. Larger than stats.active. It does not include inactive extents โ€” even ones holding unused dirty pages โ€” which means there is no strict ordering between this and stats.resident. yes, that's on purpose, no cap ๐Ÿ˜ญ
  • stats.retained (size_t) r- [--enable-stats] โ€” total bytes in virtual memory mappings that were retained instead of handed back to the OS via e.g. munmap(2) or similar. Retained virtual memory is typically untouched, decommitted, or purged, so it has no strongly associated physical memory (extent hooks have the details if you're nosy). Retained memory is excluded from mapped memory statistics, e.g. stats.mapped. it's the memory equivalent of a situationship โ€” still around, not really committed ๐Ÿ’€
  • stats.zero_reallocs (size_t) r- [--enable-stats] โ€” how many times realloc() got called with a non-NULL pointer argument and a 0 size argument. bestie, this is a fundamentally unsafe pattern in portable programs โ€” opt.zero_realloc explains why. do not do this ๐Ÿ™ˆ
  • stats.background_thread.num_threads (size_t) r- [--enable-stats] โ€” number of background threads currently running.
  • stats.background_thread.num_runs (uint64_t) r- [--enable-stats] โ€” total number of runs across all background threads.
  • stats.background_thread.run_interval (uint64_t) r- [--enable-stats] โ€” average run interval of background threads, in nanoseconds. โฑ๏ธ

๐Ÿ”’ the mutex counter moment ๐Ÿ”’

  • stats.mutexes.ctl.{counter} (counter specific type) r- [--enable-stats] โ€” statistics on the ctl mutex (global scope; mallctl related). {counter} is one of these below, ok? ๐Ÿ“‹
    • num_ops (uint64_t): total number of lock acquisition operations on this mutex.
    • num_spin_acq (uint64_t): number of times the mutex was spin-acquired. When the mutex is currently locked and can't be acquired immediately, a short period of spin-retry inside jemalloc happens. Acquired through spin generally means the contention was lightweight and didn't cause context switches. cute, quick, no drama โœจ
    • num_wait (uint64_t): number of times the mutex was wait-acquired โ€” meaning the contention wasn't solved by spin-retry, and a blocking operation was likely involved in order to get the mutex. This event generally implies higher cost / longer delay, and should be investigated if it happens often. ๐Ÿšฉ
    • max_wait_time (uint64_t): maximum length of time in nanoseconds spent on a single wait-acquired lock operation. Note that to avoid profiling overhead on the common path, this does not consider spin-acquired cases.
    • total_wait_time (uint64_t): cumulative time in nanoseconds spent on wait-acquired lock operations. Same deal โ€” spin-acquired cases aren't considered.
    • max_num_thds (uint32_t): maximum number of threads waiting on this mutex simultaneously. Again, spin-acquired cases are not considered.
    • num_owner_switch (uint64_t): number of times the current mutex owner differs from the previous one. This doesn't generally imply an issue โ€” it's more of an indicator of how often the protected data gets accessed by different threads. sharing is caring ig ๐Ÿ’…
  • stats.mutexes.background_thread.{counter} (counter specific type) r- [--enable-stats] โ€” statistics on the background_thread mutex (global scope; background_thread related). {counter} is one of the counters in mutex profiling counters.
  • stats.mutexes.prof.{counter} (counter specific type) r- [--enable-stats] โ€” statistics on the prof mutex (global scope; profiling related). {counter} is one of the counters in mutex profiling counters.
  • stats.mutexes.prof_thds_data.{counter} (counter specific type) r- [--enable-stats] โ€” statistics on the prof threads data mutex (global scope; profiling related). {counter} is one of the counters in mutex profiling counters.
  • stats.mutexes.prof_dump.{counter} (counter specific type) r- [--enable-stats] โ€” statistics on the prof dumping mutex (global scope; profiling related). {counter} is one of the counters in mutex profiling counters.
  • stats.mutexes.reset (void) -- [--enable-stats] โ€” resets all mutex profile statistics: global mutexes, arena mutexes, and bin mutexes. clean slate, we love a reset ๐Ÿงš

๐ŸŒˆ per-arena stats, aka the <i> era ๐ŸŒˆ

  • stats.arenas.<i>.dss (const char *) r- โ€” the dss (sbrk(2)) allocation precedence as related to mmap(2) allocation. See opt.dss for details.
  • stats.arenas.<i>.dirty_decay_ms (ssize_t) r- โ€” approximate time in milliseconds from when a set of unused dirty pages is created until an equivalent set of unused dirty pages is purged and/or reused. See opt.dirty_decay_ms for details. โณ
  • stats.arenas.<i>.muzzy_decay_ms (ssize_t) r- โ€” approximate time in milliseconds from when a set of unused muzzy pages is created until an equivalent set of unused muzzy pages is purged and/or reused. See opt.muzzy_decay_ms for details.
  • stats.arenas.<i>.nthreads (unsigned) r- โ€” number of threads currently assigned to the arena.
  • stats.arenas.<i>.uptime (uint64_t) r- โ€” time elapsed, in nanoseconds, since the arena was created. If <i> equals 0 or MALLCTL_ARENAS_ALL, this is the uptime since malloc initialization. baby's first birthday ๐ŸŽ‚
  • stats.arenas.<i>.pactive (size_t) r- โ€” number of pages in active extents.
  • stats.arenas.<i>.pdirty (size_t) r- โ€” number of pages within unused extents that are potentially dirty, and for which madvise() or similar has not been called. opt.dirty_decay_ms describes what dirty pages are, if you need the lore.
  • stats.arenas.<i>.pmuzzy (size_t) r- โ€” number of pages within unused extents that are muzzy. See opt.muzzy_decay_ms for what muzzy pages actually are ๐ŸŒซ๏ธ
  • stats.arenas.<i>.mapped (size_t) r- [--enable-stats] โ€” number of mapped bytes.
  • stats.arenas.<i>.retained (size_t) r- [--enable-stats] โ€” number of retained bytes. See stats.retained for details.
  • stats.arenas.<i>.extent_avail (size_t) r- [--enable-stats] โ€” number of allocated (but unused) extent structs in this arena.
  • stats.arenas.<i>.base (size_t) r- [--enable-stats] โ€” number of bytes dedicated to bootstrap-sensitive allocator metadata structures.
  • stats.arenas.<i>.internal (size_t) r- [--enable-stats] โ€” number of bytes dedicated to internal allocations. Internal allocations differ from application-originated allocations in that they're for internal use, and they're omitted from heap profiles. behind-the-scenes queen behavior ๐Ÿ‘‘
  • stats.arenas.<i>.metadata_thp (size_t) r- [--enable-stats] โ€” number of transparent huge pages (THP) used for metadata. See opt.metadata_thp for details.
  • stats.arenas.<i>.resident (size_t) r- [--enable-stats] โ€” maximum number of bytes in physically resident data pages mapped by the arena, comprising all pages dedicated to allocator metadata, pages backing active allocations, and unused dirty pages. Again โ€” a maximum rather than precise, because pages may not actually be physically resident if they correspond to demand-zeroed virtual memory that hasn't been touched yet. It's a multiple of the page size. ๐Ÿ’…
  • stats.arenas.<i>.dirty_npurge (uint64_t) r- [--enable-stats] โ€” number of dirty page purge sweeps performed.
  • stats.arenas.<i>.dirty_nmadvise (uint64_t) r- [--enable-stats] โ€” number of madvise() or similar calls made to purge dirty pages.
  • stats.arenas.<i>.dirty_purged (uint64_t) r- [--enable-stats] โ€” number of dirty pages purged.
  • stats.arenas.<i>.muzzy_npurge (uint64_t) r- [--enable-stats] โ€” number of muzzy page purge sweeps performed.
  • stats.arenas.<i>.muzzy_nmadvise (uint64_t) r- [--enable-stats] โ€” number of madvise() or similar calls made to purge muzzy pages.
  • stats.arenas.<i>.muzzy_purged (uint64_t) r- [--enable-stats] โ€” number of muzzy pages purged.
  • stats.arenas.<i>.small.allocated (size_t) r- [--enable-stats] โ€” number of bytes currently allocated by small objects. ๐Ÿ“

and we're not even done yet, bestie, hold on โ€”

  • stats.arenas.<i>.small.nmalloc (uint64_t) r- [--enable-stats] โ€” how many times, cumulatively, a small allocation got requested from the arena's bins. Either to fill the relevant tcache when opt.tcache is on, or to directly satisfy an allocation request otherwise.
  • stats.arenas.<i>.small.ndalloc (uint64_t) r- [--enable-stats] โ€” cumulative count of small allocations handed back to the arena's bins โ€” either flushing the relevant tcache when opt.tcache is on, or directly deallocating otherwise.
  • stats.arenas.<i>.small.nrequests (uint64_t) r- [--enable-stats] โ€” cumulative number of allocation requests satisfied by all bin size classes.
  • stats.arenas.<i>.small.nfills (uint64_t) r- [--enable-stats] โ€” cumulative number of tcache fills by all small size classes.
  • stats.arenas.<i>.small.nflushes (uint64_t) r- [--enable-stats] โ€” cumulative number of tcache flushes by all small size classes.
  • stats.arenas.<i>.large.allocated (size_t) r- [--enable-stats] โ€” bytes currently allocated by large objects. rn.
  • stats.arenas.<i>.large.nmalloc (uint64_t) r- [--enable-stats] โ€” cumulative count of large extents allocated from the arena โ€” whether to fill the relevant tcache if opt.tcache is enabled and the size class is within the range being cached, or to directly satisfy an allocation request otherwise.
  • stats.arenas.<i>.large.ndalloc (uint64_t) r- [--enable-stats] โ€” cumulative count of large extents returned to the arena โ€” flushing the relevant tcache if opt.tcache is enabled and the size class is in cached range, or directly deallocating otherwise. either way bestie it's counted ๐Ÿ’…
  • stats.arenas.<i>.large.nrequests (uint64_t) r- [--enable-stats] โ€” cumulative number of allocation requests satisfied by all large size classes.
  • stats.arenas.<i>.large.nfills (uint64_t) r- [--enable-stats] โ€” cumulative number of tcache fills by all large size classes.
  • stats.arenas.<i>.large.nflushes (uint64_t) r- [--enable-stats] โ€” cumulative number of tcache flushes by all large size classes.
  • stats.arenas.<i>.bins.<j>.nmalloc (uint64_t) r- [--enable-stats] โ€” cumulative count of bin regions of the corresponding size class being allocated from the arena โ€” to fill the relevant tcache when opt.tcache is on, or to directly satisfy an allocation request otherwise.
  • stats.arenas.<i>.bins.<j>.ndalloc (uint64_t) r- [--enable-stats] โ€” cumulative count of bin regions of the corresponding size class going back to the arena โ€” to flush the relevant tcache when opt.tcache is on, or to directly deallocate otherwise.
  • stats.arenas.<i>.bins.<j>.nrequests (uint64_t) r- [--enable-stats] โ€” cumulative number of allocation requests satisfied by bin regions of the corresponding size class.
  • stats.arenas.<i>.bins.<j>.curregs (size_t) r- [--enable-stats] โ€” current number of regions for this size class. live count, no cap ๐Ÿ“Š
  • stats.arenas.<i>.bins.<j>.nfills (uint64_t) r- โ€” cumulative number of tcache fills. (no --enable-stats needed, she's always on)
  • stats.arenas.<i>.bins.<j>.nflushes (uint64_t) r- โ€” cumulative number of tcache flushes. same deal โœจ
  • stats.arenas.<i>.bins.<j>.nslabs (uint64_t) r- [--enable-stats] โ€” cumulative number of slabs created.
  • stats.arenas.<i>.bins.<j>.nreslabs (uint64_t) r- [--enable-stats] โ€” cumulative number of times the current slab from which to allocate changed. the slab swapped โ€” shook.
  • stats.arenas.<i>.bins.<j>.curslabs (size_t) r- [--enable-stats] โ€” current number of slabs.
  • stats.arenas.<i>.bins.<j>.nonfull_slabs (size_t) r- [--enable-stats] โ€” current number of nonfull slabs. the ones with room left ๐Ÿซถ
  • stats.arenas.<i>.bins.<j>.mutex.{counter} (counter specific type) r- [--enable-stats] โ€” stats on the arena.<i>.bins.<j> mutex (arena bin scope; bin operation related). {counter} is one of the counters in mutex profiling counters.
  • stats.arenas.<i>.extents.<j>.n{extent_type} (size_t) r- [--enable-stats] โ€” number of extents of the given type in this arena, in the bucket corresponding to page size index <j>. The extent type is one of dirty, muzzy, or retained.
  • stats.arenas.<i>.extents.<j>.{extent_type}_bytes (size_t) r- [--enable-stats] โ€” sum of the bytes managed by extents of the given type in this arena, in the bucket corresponding to page size index <j>. Again, the extent type is one of dirty, muzzy, or retained.
  • stats.arenas.<i>.lextents.<j>.nmalloc (uint64_t) r- [--enable-stats] โ€” cumulative count of large extents of the corresponding size class being allocated from the arena โ€” filling the relevant tcache when opt.tcache is enabled and the size class is within the range being cached, or directly satisfying an allocation request otherwise.
  • stats.arenas.<i>.lextents.<j>.ndalloc (uint64_t) r- [--enable-stats] โ€” cumulative count of large extents of the corresponding size class returned to the arena โ€” flushing the relevant tcache when opt.tcache is enabled and the size class is within cached range, or directly deallocating otherwise.
  • stats.arenas.<i>.lextents.<j>.nrequests (uint64_t) r- [--enable-stats] โ€” cumulative number of allocation requests satisfied by large extents of the corresponding size class.
  • stats.arenas.<i>.lextents.<j>.curlextents (size_t) r- [--enable-stats] โ€” current number of large allocations for this size class.
  • stats.arenas.<i>.mutexes.large.{counter} (counter specific type) r- [--enable-stats] โ€” stats on the arena.<i>.large mutex (arena scope; large allocation related). {counter} is one of the counters in mutex profiling counters.
  • stats.arenas.<i>.mutexes.extent_avail.{counter} (counter specific type) r- [--enable-stats] โ€” stats on the arena.<i>.extent_avail mutex (arena scope; extent avail related). {counter} is one of the counters in mutex profiling counters.
  • stats.arenas.<i>.mutexes.extents_dirty.{counter} (counter specific type) r- [--enable-stats] โ€” stats on the arena.<i>.extents_dirty mutex (arena scope; dirty extents related). {counter} is one of the counters in mutex profiling counters.
  • stats.arenas.<i>.mutexes.extents_muzzy.{counter} (counter specific type) r- [--enable-stats] โ€” stats on the arena.<i>.extents_muzzy mutex (arena scope; muzzy extents related). {counter} is one of the counters in mutex profiling counters.
  • stats.arenas.<i>.mutexes.extents_retained.{counter} (counter specific type) r- [--enable-stats] โ€” stats on the arena.<i>.extents_retained mutex (arena scope; retained extents related). {counter} is one of the counters in mutex profiling counters.
  • stats.arenas.<i>.mutexes.decay_dirty.{counter} (counter specific type) r- [--enable-stats] โ€” stats on the arena.<i>.decay_dirty mutex (arena scope; decay for dirty pages related). {counter} is one of the counters in mutex profiling counters.
  • stats.arenas.<i>.mutexes.decay_muzzy.{counter} (counter specific type) r- [--enable-stats] โ€” stats on the arena.<i>.decay_muzzy mutex (arena scope; decay for muzzy pages related). {counter} is one of the counters in mutex profiling counters.
  • stats.arenas.<i>.mutexes.base.{counter} (counter specific type) r- [--enable-stats] โ€” stats on the arena.<i>.base mutex (arena scope; base allocator related). {counter} is one of the counters in mutex profiling counters.
  • stats.arenas.<i>.mutexes.tcache_list.{counter} (counter specific type) r- [--enable-stats] โ€” stats on the arena.<i>.tcache_list mutex (arena scope; tcache to arena association related). This mutex is expected to be accessed less often. {counter} is one of the counters in mutex profiling counters. lowkey a rare sighting ๐Ÿ‘€

๐Ÿฆ‹ HEAP PROFILE FORMAT ๐Ÿฆ‹

okay so storytime: the heap profiling functionality was originally designed to be compatible with the pprof command developed as part of the gperftools package [3]. but then per thread heap profiling got added, and that needed a different heap profile format. so now we have the jeprof command โ€” it's derived from pprof, with enhancements to support the heap profile format described right here. glow-up fr ๐Ÿ’…

In the following hypothetical heap profile, [...] indicates elision for the sake of compactness.

heap_v2/524288
  t*: 28106: 56637512 [0: 0]
  [...]
  t3: 352: 16777344 [0: 0]
  [...]
  t99: 17754: 29341640 [0: 0]
  [...]
@ 0x5f86da8 0x5f5a1dc [...] 0x29e4d4e 0xa200316 0xabb2988 [...]
  t*: 13: 6688 [0: 0]
  t3: 12: 6496 [0: 0]
  t99: 1: 192 [0: 0]
[...]

MAPPED_LIBRARIES:
[...]

The following matches the above heap profile, but most tokens are replaced with <description> to indicate descriptions of the corresponding fields.

<heap_profile_format_version>/<mean_sample_interval>
  <aggregate>: <curobjs>: <curbytes> [<cumobjs>: <cumbytes>]
  [...]
  <thread_3_aggregate>: <curobjs>: <curbytes> [<cumobjs>: <cumbytes>]
  [...]
  <thread_99_aggregate>: <curobjs>: <curbytes> [<cumobjs>: <cumbytes>]
  [...]
@ <top_frame> <frame> [...] <frame> <frame> <frame> [...]
  <backtrace_aggregate>: <curobjs>: <curbytes> [<cumobjs>: <cumbytes>]
  <backtrace_thread_3>: <curobjs>: <curbytes> [<cumobjs>: <cumbytes>]
  <backtrace_thread_99>: <curobjs>: <curbytes> [<cumobjs>: <cumbytes>]
[...]

MAPPED_LIBRARIES:
</proc/<pid>/maps>

see how it all lines up? iconic. ๐ŸŽ€


๐Ÿ’€ DEBUGGING MALLOC PROBLEMS ๐Ÿ’€

When debugging, it is a good idea to configure/build jemalloc with the --enable-debug and --enable-fill options, and recompile the program with suitable options and symbols for debugger support. When so configured, jemalloc incorporates a wide variety of run-time assertions that catch application errors such as double-free, write-after-free, etc. like a little bodyguard bestie ๐Ÿซถ

Programs often accidentally depend on "uninitialized" memory actually being filled with zero bytes. Junk filling (see the opt.junk option) tends to expose such bugs in the form of obviously incorrect results and/or coredumps. Conversely, zero filling (see the opt.zero option) eliminates the symptoms of such bugs. Between these two options, it is usually possible to quickly detect, diagnose, and eliminate such bugs. not me exposing your bugs ๐Ÿ’€

This implementation does not provide much detail about the problems it detects, because the performance impact for storing such information would be prohibitive. soz, she's fast, not chatty ๐Ÿ˜ญ

ngl the tradeoff is real: she'll catch it, she just won't write you an essay about it.

๐Ÿ’– DIAGNOSTIC MESSAGES ๐Ÿ’–

okay so bestie, if any of the memory allocation/deallocation functions clock an error or a warning condition, it prints a message to file descriptor STDERR_FILENO, no cap. Errors? Those make the process dump core. And if opt.abort is set, most warnings get treated as errors โ€” so like, sudden promotion to full error status. ๐Ÿ˜ญ

The malloc_message variable lets the programmer override the function that emits those text strings forming the errors and warnings, in case STDERR_FILENO just isn't the vibe for whatever reason. malloc_message() takes the cbopaque pointer argument โ€” which is NULL unless overridden by the arguments in a call to malloc_stats_print() โ€” followed by a string pointer. ๐Ÿซถ

โš ๏ธ please note: doing anything that tries to allocate memory in this function is likely to result in a crash or deadlock. like literally do not. ๐Ÿ™ˆ

All messages are prefixed by โ€œ<jemalloc>: โ€.


โœจ RETURN VALUES โœจ

Standard API

The malloc() and calloc() functions return a pointer to the allocated memory if successful; otherwise a NULL pointer is returned and errno is set to ENOMEM. ๐ŸŽ€

The posix_memalign() function returns the value 0 if successful; otherwise it returns an error value. The posix_memalign() function will fail if:

  • EINVAL โ€” The alignment parameter is not a power of 2 at least as large as sizeof(void *). like it's giving "not a valid power of two, bestie" ๐Ÿ’…
  • ENOMEM โ€” Memory allocation error. ๐Ÿ’€

The aligned_alloc() function returns a pointer to the allocated memory if successful; otherwise a NULL pointer is returned and errno is set. The aligned_alloc() function will fail if:

  • EINVAL โ€” The alignment parameter is not a power of 2. (note: no sizeof(void *) clause here, that's posix_memalign()'s whole thing, iykyk)
  • ENOMEM โ€” Memory allocation error.

The realloc() function returns a pointer, possibly identical to ptr, to the allocated memory if successful; otherwise a NULL pointer is returned, and errno is set to ENOMEM if the error was the result of an allocation failure. The realloc() function always leaves the original buffer intact when an error occurs. ๐Ÿ›Ÿ that's the safety net, periodt.

The free() function returns no value. iconique, unbothered, returns nothing. ๐ŸŒธ

Non-standard API

The mallocx() and rallocx() functions return a pointer to the allocated memory if successful; otherwise a NULL pointer is returned to indicate insufficient contiguous memory was available to service the allocation request.

The xallocx() function returns the real size of the resulting resized allocation pointed to by ptr, which is a value less than size if the allocation could not be adequately grown in place.

The sallocx() function returns the real size of the allocation pointed to by ptr.

The nallocx() returns the real size that would result from a successful equivalent mallocx() function call, or zero if insufficient memory is available to perform the size computation. ๐Ÿงฎ

The mallctl(), mallctlnametomib(), and mallctlbymib() functions return 0 on success; otherwise they return an error value. The functions will fail if:

  • EINVAL โ€” newp is not NULL, and newlen is too large or too small. Alternatively, *oldlenp is too large or too small; when it happens, except for a very few cases explicitly documented otherwise, as much data as possible are read despite the error, with the amount of data read being recorded in *oldlenp. ๐Ÿซ 
  • ENOENT โ€” name or mib specifies an unknown/invalid value.
  • EPERM โ€” Attempt to read or write void value, or attempt to write read-only value. ๐Ÿ”’
  • EAGAIN โ€” A memory allocation failure occurred.
  • EFAULT โ€” An interface with side effects failed in some way not directly related to mallctl*() read/write processing.

The malloc_usable_size() function returns the usable size of the allocation pointed to by ptr. ๐ŸŒˆ


๐ŸŒฟ ENVIRONMENT ๐ŸŒฟ

The following environment variable affects the execution of the allocation functions:

  • MALLOC_CONF โ€” If the environment variable MALLOC_CONF is set, the characters it contains will be interpreted as options. ๐Ÿ‘‘

๐Ÿฆ‹ EXAMPLES ๐Ÿฆ‹

To dump core whenever a problem occurs:

ln -s 'abort:true' /etc/malloc.conf

To specify in the source that only one arena should be automatically created:

malloc_conf = "narenas:1";

that's it, that's the rizz. ๐ŸŽ€


๐Ÿ”ฅ SEE ALSO ๐Ÿ”ฅ

madvise(2), mmap(2), sbrk(2), utrace(2), alloca(3), atexit(3), getpagesize(3)

go read those next bestie, i believe in you ๐Ÿซถ


๐Ÿ‘‘ STANDARDS ๐Ÿ‘‘

The malloc(), calloc(), realloc(), and free() functions conform to ISO/IEC 9899:1990 (โ€œISO C90โ€).

The posix_memalign() function conforms to IEEE Std 1003.1-2001 (โ€œPOSIX.1โ€).

certified, standardized, official. ๐Ÿ’…


โญ HISTORY โญ

The malloc_usable_size() and posix_memalign() functions first appeared in FreeBSD 7.0.

The aligned_alloc(), malloc_stats_print(), and mallctl*() functions first appeared in FreeBSD 10.0.

The *allocx() functions first appeared in FreeBSD 11.0.

the lore is honestly so real ๐Ÿงš


๐Ÿ’œ AUTHOR ๐Ÿ’œ

Jason Evans

(genuinely the main character of this whole manpage ngl ๐Ÿ‘‘)


๐Ÿ“ NOTES ๐Ÿ“

  1. jemalloc website http://jemalloc.net/
  2. JSON format http://www.json.org/
  3. gperftools package http://code.google.com/p/gperftools/

and that's a wrap on this whole gorgeous beast of a manpage!! ๐Ÿ’– this was jemalloc 5.3.0-0-g54eaed1d8b56, dated 05/06/2022, and you can cite it as JEMALLOC(3) โ€” slay, go forth and allocate responsibly bestie ๐Ÿฆ‹โœจ๐ŸŽ€

๐Ÿ“ฆ where this page comes from