JEMALLOC(3)
๐ 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 yousizebytes 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 fornumberobjects, eachsizebytes long. the result is identical to callingmalloc()with an argument ofnumber * size, except โ and this is the slay part โ the allocated memory is explicitly initialized to zero bytes. free cleanup, periodt.posix_memalign()โ allocatessizebytes such that the allocation's base address is a multiple ofalignment, and returns the allocation in the value pointed to byptr. the requestedalignmentmust be a power of 2 at least as large assizeof(void *). yes, that's a hard requirement, don't come for me.aligned_alloc()โ allocatessizebytes of memory such that the allocation's base address is a multiple ofalignment. the requestedalignmentmust be a power of 2. โ ๏ธ behavior is undefined ifsizeis not an integral multiple ofalignmentโ so like, don't do that, bestie.realloc()โ changes the size of the previously allocated memory referenced byptrtosizebytes. 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 byptris freed and a pointer to the newly allocated memory is returned. note thatrealloc()may move the memory allocation, resulting in a different return value thanptrโ so literally update your pointer, i promise it's not that deep. and ifptrisNULL,realloc()behaves identically tomalloc()for the specifiedsize. cute.free()โ causes the allocated memory referenced byptrto be made available for future allocations. ifptrisNULL, 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 thatlais 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 ofa, whereais a power of two. same vibe check: this macro does not validate thatais 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 identifiertc, which must have been acquired via thetcache.createmallctl. this macro does not validate thattcspecifies a valid identifier. again: trust fall.MALLOCX_TCACHE_NONEโ do not use a thread-specific cache (tcache). unlessMALLOCX_TCACHE(tc)orMALLOCX_TCACHE_NONEis specified, an automatically managed tcache will be used under many circumstances. ๐จ this macro cannot be used in the sameflagsargument asMALLOCX_TCACHE(tc)โ they're giving enemies to lovers, pick one.MALLOCX_ARENA(a)โ use the arena specified by the indexa. this macro has no effect for regions that were allocated via an arena other than the one specified. and this macro does not validate thataspecifies an arena index in the valid range.
okay now the functions themselves, in order โจ
mallocx()โ allocates at leastsizebytes of memory, and returns a pointer to the base address of the allocation. behavior is undefined ifsizeis 0. so like... don't pass 0.rallocx()โ resizes the allocation atptrto be at leastsizebytes, 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 ifsizeis 0.xallocx()โ resizes the allocation atptrin place to be at leastsizebytes, and returns the real size of the allocation. ifextrais 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 ifsizeis 0, or if(size + extra > SIZE_T_MAX). ๐ respect the limits.sallocx()โ returns the real size of the allocation atptr. that's it, that's the whole function, and honestly? iconic minimalism.dallocx()โ causes the memory referenced byptrto be made available for future allocations. bye bestie ๐sdallocx()โ an extension ofdallocx()with asizeparameter 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 bynallocx()orsallocx(). so you gotta know your size range, iykyk.nallocx()โ allocates no memory (yes really, zero), but it performs the same size computation as themallocx()function, and returns the real size of the allocation that would result from the equivalentmallocx()function call, or0if the inputs exceed the maximum supported size class and/or alignment. behavior is undefined ifsizeis 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-separatednameargument 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 viaoldpto adequate space to contain the value, and a pointer to its length viaoldlenp; otherwise passNULLandNULL. similarly, to write a value, pass a pointer to the value vianewp, and its length vianewlen; otherwise passNULLand0. 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 tomallctlbymib(). upon successful return frommallctlnametomib(),mibpcontains an array of*miblenpintegers, where*miblenpis the lesser of the number of components innameand the input value of*miblenp. thus it is possible to pass a*miblenpthat is smaller than the number of period-separatednamecomponents, which results in a partial MIB that can be used as the basis for constructing a complete MIB. fornamecomponents that are integers (e.g. the2inarenas.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 thewrite_cbcallback function pointer andcbopaquedata passed towrite_cb, ormalloc_message()ifwrite_cbisNULL. the statistics are presented in human-readable form unless "J" is specified as a character within theoptsstring, 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 theoptsstring.
okay so this next bit is a whole situation, stay with me:malloc_stats_print()uses themallctl*()functions internally, so inconsistent statistics can be reported if multiple threads use these functions simultaneously. if--enable-statsis 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 byptr. the return value may be larger than the size that was requested during allocation. ๐จ important:malloc_usable_size()is not a mechanism for in-placerealloc(); rather it is provided solely as a tool for introspection purposes. any discrepancy between the requested allocation size and the size reported bymalloc_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 thatmalloc_confmay be read beforemain()is entered, so the declaration ofmalloc_confshould specify an initializer that contains the final value to be read by jemalloc. also:--with-malloc-confandmalloc_confare compile-time mechanisms, whereas/etc/malloc.confandMALLOC_CONFcan 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 (
8or16, depending on architecture), it gets rounded up to the nearest power of two that is at leastsizeof(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 from0to 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 theepochmallctl, which controls refreshing of cached dynamic statistics. Like, actually take note. Stats brain rot is real andepochis 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 themallctl*()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 totrueand 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: afterfork(2), the state in the child process will be disabled regardless of the state in the parent process. Seestats.background_threadfor the related stats.opt.background_threadcan 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 atopt.max_background_threadsat startup. โจconfig.cache_oblivious(bool) r- โ--enable-cache-obliviouswas specified during build configuration.config.debug(bool) r- โ--enable-debugwas specified during build configuration.config.fill(bool) r- โ--enable-fillwas specified during build configuration.config.lazy_lock(bool) r- โ--enable-lazy-lockwas specified during build configuration.config.malloc_conf(const char *) r- โ embedded configure-time-specified run-time options string ๐ โ it's empty unless--with-malloc-confwas specified during build configuration.config.prof(bool) r- โ--enable-profwas specified during build configuration.config.prof_libgcc(bool) r- โ--disable-prof-libgccwas not specified during build configuration.config.prof_libunwind(bool) r- โ--enable-prof-libunwindwas specified during build configuration.config.stats(bool) r- โ--enable-statswas specified during build configuration.config.utrace(bool) r- โ--enable-utracewas specified during build configuration.config.xmalloc(bool) r- โ--enable-xmallocwas specified during build configuration.
the opt.* girlies ๐ธ
opt.abort(bool) r- โ abort-on-warning, enabled/disabled. Iftrue, most warnings are fatal ๐ญ. Note that runtime option warnings are not included (seeopt.abort_conffor that one). The process will callabort(3)in these cases. This option is disabled by default โ unless--enable-debugis specified during configuration, in which case it is enabled by default.opt.confirm_conf(bool) r- โ confirm-runtime-options-when-program-starts, enabled/disabled. Iftrue, the string specified via--with-malloc-conf, the string pointed to by the global variablemalloc_conf, the โnameโ of the file referenced by the symbolic link named/etc/malloc.conf, and the value of the environment variableMALLOC_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. Iftrue, invalid runtime options are fatal ๐. The process will callabort(3)in these cases. This option is disabled by default โ unless--enable-debugis 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 the16 KiBsize class to20 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 (seestats.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- โ iftrue, do not perform the runtime check forMADV_DONTNEED, which checks that it actually zeros pages. The default is disabled on Linux and enabled elsewhere. ๐งopt.retain(bool) r- โ iftrue, retain unused virtual memory for later reuse rather than discarding it by callingmunmap(2)or equivalent (seestats.retainedfor related details). It also makes jemalloc usemmap(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:
- 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
- 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 tommap(2)allocation. The following settings are supported ifsbrk(2)is supported by the operating system:"disabled","primary", and"secondary"; otherwise only"disabled"is supported. The default is"secondary"ifsbrk(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 withinnarenas), 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 viaMALLOCX_ARENA, or threads associated with explicit arenas, will not be considered. The default threshold is8MiB. 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, seebackground_threadfor 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 ifbackground_threadis 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 of0causes all unused dirty pages to be purged immediately upon creation. A decay time of-1disables purging. The default decay time is 10 seconds. Seearenas.dirty_decay_msandarena.<i>.dirty_decay_msfor related dynamic control options. Seeopt.muzzy_decay_msfor a description of muzzy pages. Note that when theoversize_thresholdfeature 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 of0causes all unused muzzy pages to be purged immediately upon creation. A decay time of-1disables purging. The default decay time is 10 seconds. Seearenas.muzzy_decay_msandarena.<i>.muzzy_decay_msfor 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 is6, which gives a maximum ratio of64(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 anatexit(3)function, and you can pair it withopt.stats_print_optsto pick the output options. โ ๏ธ big warning tho: if--enable-statswas 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 callsatexit(), so this option is not universally usable (tho the application can register its ownatexit()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 (theoptsstring) to pass to themalloc_stats_print()at exit (enabled throughopt.stats_print). See available options inmalloc_stats_print(). Has no effect unlessopt.stats_printis 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 callsmalloc_stats_print(). You can combine it withopt.stats_interval_optsto specify output options. By default, interval-triggered stats output is disabled (encoded as-1). โจopt.stats_interval_opts(const char *) r-โ the options (theoptsstring) to pass tomalloc_stats_print()for interval based statistics printing (enabled throughopt.stats_interval). See available options inmalloc_stats_print(). Has no effect unlessopt.stats_intervalis 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 to0xa5."free"โ all deallocated memory will be initialized to0x5a."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 to0. Note that this initialization only happens once for each byte, sorealloc()andrallocx()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 onutrace(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 onSTDERR_FILENOand cause the program to drop core (usingabort(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 theopt.tcache_maxoption 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 to8 MiBcan be cached. The default maximum is32 KiB(2^15). As a convenience, this may also be set by specifyinglg_tcache_max, which will be taken to be the base-2 logarithm of the setting oftcache_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 withMADV_HUGEPAGE."never"ensures no transparent hugepage withMADV_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 theopt.prof_activeoption for on-the-fly activation/deactivation. See theopt.lg_prof_sampleoption for probabilistic sampling control. See theopt.prof_accumoption for control of cumulative sample reporting. See theopt.lg_prof_intervaloption for information on interval-triggered profile dumping, theopt.prof_gdumpoption for information on high-water-triggered profile dumping, and theopt.prof_finaloption for final profile dumping. Profile output is compatible with thejeprofcommand, which is based on thepprofthat is developed as part of thegperftoolspackage[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 isjeprof. This prefix value can be overridden byprof.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 theopt.profoption) but inactive, then toggle profiling at any time during program execution with theprof.activemallctl. Enabled by default.opt.prof_thread_active_init(bool) r-[--enable-prof]โ initial setting forthread.prof.activein newly created threads. The initial setting for newly created threads can also be changed during execution via theprof.thread_active_initmallctl. 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 is512 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 theopt.prof_prefixandprof.prefixoptions. By default, interval-triggered profile dumping is disabled (encoded as-1). ๐opt.prof_gdump(bool) r-[--enable-prof]โ set the initial state ofprof.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 anatexit(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 theopt.prof_prefixandprof.prefixoptions. Note thatatexit()may allocate memory during application initialization and then deadlock internally when jemalloc in turn callsatexit(), so this option is not universally usable (though the application can register its ownatexit()function with equivalent functionality). Disabled by default. ๐ญopt.prof_leak(bool) r-[--enable-prof]โ leak reporting enabled/disabled. If enabled, use anatexit(3)function to report memory leaks detected by allocation sampling. See theopt.profoption for information on analyzing heap profile output. Works only when combined withopt.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 toopt.prof_leak, but makes the process exit with error code1if a memory leak is detected. This option supersedesopt.prof_leak, meaning that if both are specified, this option takes precedence. When enabled, also enablesopt.prof_leak. Works only when combined withopt.prof_final, otherwise does nothing. Disabled by default. ๐opt.zero_realloc(const char *) r-โ determines the behavior ofrealloc()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 returnsNULLwithout settingerrno."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 thearena.i.initializedmallctl), 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 thethread.allocatedmallctl. Iconic for skipping the overhead of callingmallctl*()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 thethread.deallocatedmallctl. Again: avoids the overhead of repeatedmallctl*()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 lastthread.peak.resetcall, or since the thread was created if it never calledthread.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 laterthread.peak.readcalls, but not the values you get fromthread.allocatedorthread.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 (seethread.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 byisgraph(3)andisblank(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 ofprof.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 theMALLOCX_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 forthread.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 toMALLCTL_ARENAS_ALLare 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>equalsMALLCTL_ARENAS_ALL. How much of the unused dirty/muzzy pages gets purged depends on the current time; seeopt.dirty_decay_msandopt.muzy_decay_msfor the deets. โณarena.<i>.purge(void) -- โ purge all unused dirty pages for arena<i>, or for all arenas if<i>equalsMALLCTL_ARENAS_ALL. ๐งฝarena.<i>.reset(void) -- โ discard all of the arena's extant allocations. Only usable with arenas explicitly created viaarenas.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 asarena.<i>.reset(with all the same constraints and side effects), merges the arena stats into the ones accessible at arena indexMALLCTL_ARENAS_DESTROYED, and then completely discards all metadata associated with the arena. Futurearenas.createcalls may recycle that arena index. Destruction will fail if any threads are currently associated with the arena as a result ofthread.arenacalls. ๐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>equalsMALLCTL_ARENAS_ALL. Seeopt.dssfor 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). Seeopt.dirty_decay_msfor 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). Seeopt.muzzy_decay_msfor more. ๐ซ๏ธarena.<i>.retain_grow_limit(size_t) rw โ the maximum size to grow the retained region (only relevant whenopt.retainis enabled). This controls the maximum increment for expanding virtual memory, or allocation througharena.<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 viaarenas.createso 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 initializearena.<i>.dirty_decay_msduring arena creation. seeopt.dirty_decay_msfor 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 initializearena.<i>.muzzy_decay_msduring arena creation. seeopt.muzzy_decay_msfor 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 equalsizeof(unsigned), no arena will be created, no data will be written to the space pointed byoldp, and*oldlenpwill be set to0.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 forthread.prof.activein newly created threads. see theopt.prof_thread_active_initoption for additional information.prof.active(bool)rw[--enable-prof]โ control whether sampling is currently active. see theopt.prof_activeoption for additional information, as well as the interrelatedthread.prof.activemallctl.prof.dump(const char *) -w[--enable-prof]โ dumps a memory profile into the file you name. If you passNULLbestie, it makes its own filename following the pattern<prefix>.<pid>.<seq>.m<mseq>.heap, where<prefix>comes from theopt.prof_prefixandprof.prefixoptions. iconic, it names itself โจprof.prefix(const char *) -w[--enable-prof]โ sets the filename prefix for profile dumps. Peepopt.prof_prefixfor 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>fromopt.prof_prefixandprof.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 (seeopt.lg_prof_sampleandprof.lg_sample). fresh start energy ๐ธprof.lg_sample(size_t) r-[--enable-prof]โ reads back the current sample rate (seeopt.lg_prof_sample).prof.interval(uint64_t) r-[--enable-prof]โ the average number of bytes allocated between interval-based profile dumps.opt.lg_prof_intervalhas 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 tostats.allocated. It does not includestats.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 (seestats.arenas.<i>.base) plus internal allocations (seestats.arenas.<i>.internal). Transparent huge page usage (enabled withopt.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. Seestats.metadataandopt.metadata_thpfor 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 thanstats.active. it's giving "theoretical roof" ๐stats.mapped(size_t) r-[--enable-stats]โ total bytes in active extents mapped by the allocator. Larger thanstats.active. It does not include inactive extents โ even ones holding unused dirty pages โ which means there is no strict ordering between this andstats.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 timesrealloc()got called with a non-NULLpointer argument and a0size argument. bestie, this is a fundamentally unsafe pattern in portable programs โopt.zero_reallocexplains 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 thectlmutex (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 thebackground_threadmutex (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 theprofmutex (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 theprofthreads 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 theprofdumping 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- โ thedss(sbrk(2)) allocation precedence as related tommap(2)allocation. Seeopt.dssfor 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. Seeopt.dirty_decay_msfor 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. Seeopt.muzzy_decay_msfor 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>equals0orMALLCTL_ARENAS_ALL, this is the uptime sincemallocinitialization. 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 whichmadvise()or similar has not been called.opt.dirty_decay_msdescribes 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. Seeopt.muzzy_decay_msfor 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. Seestats.retainedfor 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. Seeopt.metadata_thpfor 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 ofmadvise()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 ofmadvise()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 whenopt.tcacheis 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 whenopt.tcacheis 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 ifopt.tcacheis 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 ifopt.tcacheis 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 whenopt.tcacheis 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 whenopt.tcacheis 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-statsneeded, 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 thearena.<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 ofdirty,muzzy, orretained.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 ofdirty,muzzy, orretained.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 whenopt.tcacheis 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 whenopt.tcacheis 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 thearena.<i>.largemutex (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 thearena.<i>.extent_availmutex (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 thearena.<i>.extents_dirtymutex (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 thearena.<i>.extents_muzzymutex (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 thearena.<i>.extents_retainedmutex (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 thearena.<i>.decay_dirtymutex (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 thearena.<i>.decay_muzzymutex (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 thearena.<i>.basemutex (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 thearena.<i>.tcache_listmutex (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 assizeof(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: nosizeof(void *)clause here, that'sposix_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 notNULL, and newlen is too large or too small. Alternatively,*oldlenpis 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 tomallctl*()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 variableMALLOC_CONFis 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 ๐
- jemalloc website http://jemalloc.net/
- JSON format http://www.json.org/
- 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
- freebsd 15.1: base 15.1
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms