JEMALLOC(3)
okay so bestie, this is the jemalloc(3) manual β general purpose memory allocation functions, and she's literally the blueprint. π We're breaking down part 1, so let's get into it. β¨
π NAME π
jemalloc β general purpose memory allocation functions. That's the name, that's the vibe. π
π LIBRARY π
This manual describes jemalloc 5.3.0-0-g54eaed1d8b56b1aa528be3bdd1877e59c56fa90c. More information can be found at the jemalloc website[1]. Slay. π
π SYNOPSIS π
First things first, you gotta include the header:
#include <jemalloc/jemalloc.h>
β¨ Standard API β¨
Here are the standard besties:
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 squad:
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 π¦
π Standard API π
The malloc() function allocates size bytes of uninitialized memory. The allocated space is suitably aligned (after possible pointer coercion) for storage of any type of object. So like, it gives you memory, but it's not zeroed out β it's whatever was there before. π
The calloc() function allocates space for number objects, each size bytes in length. The result is identical to calling malloc() with an argument of number * size, with the exception that the allocated memory is explicitly initialized to zero bytes. So it's malloc but with a clean slate. β¨
The posix_memalign() function allocates size bytes of memory 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 *). So you get fancy aligned memory, but don't try any funny business with the alignment. π«
The aligned_alloc() function 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. > bestie, undefined behavior is not the vibe. Make sure size is a multiple of alignment, fr fr. π
The realloc() function changes the size of the previously allocated memory referenced by ptr to size bytes. The contents of the memory 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 of the memory 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. If ptr is NULL, the realloc() function behaves identically to malloc() for the specified size. So it's like a glow-up for your memory, but sometimes it moves to a new address. π
The free() function causes the allocated memory referenced by ptr to be made available for future allocations. If ptr is NULL, no action occurs. So you're releasing it back to the universe. π
π Non-standard API π
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. 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). This macro does not validate thatlais within the valid range. So like, you're on your own with that one. πMALLOCX_ALIGN(a)β Align the memory allocation to start at an address that is a multiple ofa, whereais a power of two. This macro does not validate thatais a power of 2. No cap, it just trusts you. π«Ά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. If this macro is absent, newly allocated memory is uninitialized. So it's likecallocbut formallocx. β¨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. You gotta make sure it's valid, bestie. π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). So pick one, okay? π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. This macro does not validate thataspecifies an arena index in the valid range. Again, no validation, so be careful. π₯
The mallocx() function 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. > zero size? undefined. don't do it. π
The rallocx() function 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. Same energy. π
The xallocx() function 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). So watch out for that overflow. π¨
The sallocx() function returns the real size of the allocation at ptr. Simple. π
The dallocx() function causes the memory referenced by ptr to be made available for future allocations. It's like free but for the non-standard crew. π
The sdallocx() function is 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 can speed things up if you know the size. β¨
The nallocx() function allocates no memory, 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. So it's like a preview of what you'd get. π¬
The mallctl() function 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. So it's your all-access pass to the allocator's inner world. π
The mallctlnametomib() function 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... */
}
The malloc_stats_print() function 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. Note that 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. 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 it's a whole vibe, but watch out for those stats being a little behind. π
The malloc_usable_size() function 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. The malloc_usable_size() function 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 don't rely on it for anything but peeking. π
π TUNING π
okay bestie, so here's the tea β β the very first time anybody calls one of the memory allocation routines, the allocator has to wake up and initialize its whole internal situation π§ β¨ and it does that based in part on a bunch of options you can hand it at compile-time or run-time. she's customizable, she's got range, she's everything π
πΈ where the options come from (in order, no skipping)
These four sources get interpreted in that order, from left to right, as options:
- 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 - the value of the environment variable
MALLOC_CONF
β οΈ heads up bestie:malloc_confmay be read beforemain()is entered π so when you declaremalloc_conf, you gotta specify an initializer that already contains the final value to be read by jemalloc. no lazy defaults, she wants the finished look β¨
also, key distinction because iykyk: --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 π«Ά
π¦ how to actually write an options string
an options string is just a comma-separated list of option:value pairs. that's it. that's the whole vibe π
there's one key corresponding to each opt.* mallctl (go peek at the MALLCTL NAMESPACE section for the actual options documentation, she spills everything there π).
like, for example:
abort:true,narenas:1
that lil string sets the opt.abort and opt.narenas options. iconic, right? two birds, one comma π
π the value types (she contains multitudes)
not every option wants the same kind of value, and honestly same:
- some options have boolean values β that's
true/false, binary queen π - some have integer values β and these get read in base 8, 10, or 16, depending on the prefix π’
- and yet others have raw string values β no math, just vibes πΈ
so yeah, know your type before you type it, bestie π
β¨ IMPLEMENTATION NOTES β¨
okay so bestie, let's talk about why this allocator is built the way it is, because the vibes are immaculate and also extremely deliberate π
Traditionally, allocators have used sbrk(2) to get memory, and ngl that's kind of a flop for a bunch of reasons: race conditions, more fragmentation, and artificial caps on how much memory you can actually use. So here's the tea β if the operating system supports sbrk(2), this allocator uses both mmap(2) and sbrk(2), in that exact order of preference. If not? Only mmap(2). Periodt.
This allocator uses multiple arenas to cut down on lock contention for threaded programs on multi-processor systems. That's a slay for threading scalability, but it does come with a lil cost: there's a small fixed per-arena overhead, and on top of that, arenas manage memory completely independently of each other, which means a small fixed increase in overall memory fragmentation. Honestly? These overheads are not usually a problem given how many arenas get used normally. Also, using substantially more arenas than the default is not likely to improve performance β mostly because of reduced cache performance, iykyk. But it can make sense to reduce the number of arenas if your app isn't really using the allocation functions much.
On top of multiple arenas, this allocator supports thread-specific caching, which makes it possible to completely avoid synchronization for most allocation requests π. That caching = very fast allocation in the common case, but it does increase memory usage and fragmentation, since a bounded number of objects can stay allocated in each thread cache. Trade-offs, bestie!
Memory is conceptually broken into extents. Extents are always aligned to multiples of the page size, and that alignment is what makes it possible to find metadata for user objects quickly. User objects split into two categories by size: small and large. Contiguous small objects make up a slab, which lives inside a single extent, whereas large objects each get their own extents backing them.
Small objects are managed in groups by slabs. Each slab keeps a bitmap to track which regions are in use. Allocation requests that are no more than half the quantum (8 or 16, depending on architecture) get rounded up to the nearest power of two that is at least sizeof(double). Every other object size class is a multiple of the quantum, spaced so 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, and large size classes extend from four times the page size all the way up to the largest size class that doesn't exceed PTRDIFF_MAX.
Allocations are packed tightly together, and that can be an issue for multi-threaded applications π. If you need to make sure your allocations don't suffer from cacheline sharing, round your allocation requests up to the nearest multiple of the cacheline size, or specify cacheline alignment when allocating. Do that.
The realloc(), rallocx(), and xallocx() functions may resize allocations without moving them under limited circumstances. 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 you have to call realloc() to grow, like, a 9-byte allocation to 16 bytes, or shrink a 16-byte allocation to 9 bytes. Growth and shrinkage trivially succeeds in place as long as the pre-size and post-size both round up to the same size class. 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.
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 π
πΈ Table 1. Size classes πΈ
Category: Small
- Spacing
lgβ Size[8] - Spacing
16β Size[16, 32, 48, 64, 80, 96, 112, 128] - Spacing
32β Size[160, 192, 224, 256] - Spacing
64β Size[320, 384, 448, 512] - Spacing
128β Size[640, 768, 896, 1024] - Spacing
256β Size[1280, 1536, 1792, 2048] - Spacing
512β Size[2560, 3072, 3584, 4096] - Spacing
1 KiBβ Size[5 KiB, 6 KiB, 7 KiB, 8 KiB] - Spacing
2 KiBβ Size[10 KiB, 12 KiB, 14 KiB]
Category: Large
- Spacing
2 KiBβ Size[16 KiB] - Spacing
4 KiBβ Size[20 KiB, 24 KiB, 28 KiB, 32 KiB] - Spacing
8 KiBβ Size[40 KiB, 48 KiB, 56 KiB, 64 KiB] - Spacing
16 KiBβ Size[80 KiB, 96 KiB, 112 KiB, 128 KiB] - Spacing
32 KiBβ Size[160 KiB, 192 KiB, 224 KiB, 256 KiB] - Spacing
64 KiBβ Size[320 KiB, 384 KiB, 448 KiB, 512 KiB] - Spacing
128 KiBβ Size[640 KiB, 768 KiB, 896 KiB, 1 MiB] - Spacing
256 KiBβ Size[1280 KiB, 1536 KiB, 1792 KiB, 2 MiB] - Spacing
512 KiBβ Size[2560 KiB, 3 MiB, 3584 KiB, 4 MiB] - Spacing
1 MiBβ Size[5 MiB, 6 MiB, 7 MiB, 8 MiB] - Spacing
2 MiBβ Size[10 MiB, 12 MiB, 14 MiB, 16 MiB] - Spacing
4 MiBβ Size[20 MiB, 24 MiB, 28 MiB, 32 MiB] - Spacing
8 MiBβ Size[40 MiB, 48 MiB, 56 MiB, 64 MiB] - Spacing
...β Size... - Spacing
512 PiBβ Size[2560 PiB, 3 EiB, 3584 PiB, 4 EiB] - Spacing
1 EiBβ Size[5 EiB, 6 EiB, 7 EiB]
π¦ MALLCTL NAMESPACE π¦
These names are defined in the namespace you can reach via the mallctl*() functions π«Ά. Value types are given in parentheses, their readable/writable statuses are encoded as rw, r-, -w, or --, and required build configuration flags follow, if any. A name element encoded as <i> or <j> means an integer component, where the integer varies from 0 to some upper value that you have to figure out via introspection, no cap.
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 to 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 used either via mallctlnametomib() followed by mallctlbymib(), or via code like this:
#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. It's the main character here, fr fr.
version(const char *)r-β Return the jemalloc version string πepoch(uint64_t) rw β okay so if you actually pass a value in, it refreshes all the data that themallctl*()functions report from, and increments the epoch. Then it returns the current epoch. This is so useful for detecting whether another thread caused a refresh. Detective work, but make it β¨memoryβ¨. π΅οΈββοΈbackground_thread(bool) rw β turn internal background worker threads on/off. When you set it totrue, background threads get created on demand (and the number of them will be no more than the number of CPUs or the number of active arenas, whichever). Those threads run periodically and handle purging asynchronously. When you switch it off, background threads are terminated synchronously. Heads up bestie: afterfork(2), the state in the child process will be disabled no matter what the state in the parent process was. Seestats.background_threadfor the related stats, andopt.background_threadif you wanna set the default option. Also, this one is only available on selected pthread-based platforms β iykyk. π§΅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. So like, don't get any ideas. π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- β the 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.
okay and now we're in opt.* territory, aka the build-time defaults, aka the "you can only read these, they're not taking suggestions" zone. π«π
opt.abort(bool) r- β abort-on-warning, enabled/disabled. Iftrue, most warnings are fatal. Note that runtime option warnings are not included in this (seeopt.abort_conffor that one). The process will callabort(3)in these cases. This option is disabled by default β unless--enable-debugwas 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, all of these get printed, in order: 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. Then, each option being set will be individually printed too. Loud and proud, fr fr. This option is disabled by default.opt.abort_conf(bool) r- β abort-on-invalid-configuration, enabled/disabled. Iftrue, invalid runtime options are fatal, and the process will callabort(3)in these cases. This option is disabled by default β unless--enable-debugwas 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 here's the trade-off bestie: 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 to allow jemalloc 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 the 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, andβdisabledβotherwise.opt.narenas(unsigned) r- β the 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. math is mathing π’opt.oversize_threshold(size_t) r- β the threshold in bytes at 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 an 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β,narenasand the 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 crash or deadlock during initialization β οΈ β so 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- β the maximum number of background threads that will be created ifbackground_threadis set. Defaults to the number of cpus.opt.dirty_decay_ms(ssize_t) r- β the 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 is10seconds. Seearenas.dirty_decay_msandarena.<i>.dirty_decay_msfor the 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- β the 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 is10seconds. Seearenas.muzzy_decay_msandarena.<i>.muzzy_decay_msfor the 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). A 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-β on/off switch for printing stats when the program exits. When it's on,malloc_stats_print()gets called at program exit through anatexit(3)function. You can pair it withopt.stats_print_optsto pick what the output looks like. π«Ά
β οΈ okay but hear me out: 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. And on top of that,atexit()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). So likeβ¦ use it with care, fr. It's primarily intended as a performance tuning aid during application development. This option is disabled by default.
opt.stats_print_opts(const char *)r-β the options (the opts string) you pass tomalloc_stats_print()at exit (which is enabled throughopt.stats_print). Check the available options over inmalloc_stats_print(). β¨ Has no effect unlessopt.stats_printis enabled. The default is"".opt.stats_interval(int64_t)r-β average interval between statistics outputs, measured in bytes of allocation activity. Heads up: the actual interval may be sporadic because decentralized event counters are used to avoid synchronization bottlenecks. 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 (the opts string) you 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. The default is"".opt.junk(const char *)r-[--enable-fill]β junk filling, bestie. π If it's set to"alloc", each byte of uninitialized allocated memory will be initialized to0xa5. If it's set to"free", all deallocated memory will be initialized to0x5a. If it's set to"true", both allocated and deallocated memory will be initialized, and if it's set to"false", junk filling is disabled entirely. This is intended for debugging and will impact performance negatively. This option is"false"by default unless--enable-debugis specified during configuration, in which case it is"true"by default.opt.zero(bool)r-[--enable-fill]β zero filling on/off. When it's on, each byte of uninitialized allocated memory gets initialized to0. Note that this initialization only happens once for each byte, sorealloc()andrallocx()calls do not zero memory that was previously allocated. This is intended for debugging and will impact performance negatively. This option is disabled by default.opt.utrace(bool)r-[--enable-utrace]β allocation tracing based onutrace(2), on/off. This option is disabled by default.opt.xmalloc(bool)r-[--enable-xmalloc]β abort-on-out-of-memory, on/off. If it's enabled, rather than returning failure for any allocation function, it displays a diagnostic message onSTDERR_FILENOand causes 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), on/off. 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. This option is enabled by default.opt.tcache_max(size_t)r-β the 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. The"always"setting enables transparent hugepage for all user memory mappings withMADV_HUGEPAGE;"never"ensures no transparent hugepage withMADV_NOHUGEPAGE; the default setting"default"makes no changes. Note that: this option does not affect THP for jemalloc internal metadata (seeopt.metadata_thp); in addition, for arenas with customizedextent_hooks, this option is bypassed as it is implemented as part of the default extent hooks. πopt.prof(bool)r-[--enable-prof]β memory profiling, on/off. If it's 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. This option is enabled by default.opt.prof_thread_active_init(bool)r-[--enable-prof]β the 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. This option is enabled by default.opt.lg_prof_sample(size_t)r-[--enable-prof]β average interval (log base 2) between allocation samples, as measured in bytes of allocation activity. Increasing the sampling interval decreases profile fidelity, but also decreases the computational overhead. 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, on/off. 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. This option is disabled by default.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]β sets the initial state ofprof.gdump, which when enabled triggers a memory profile dump every time the total virtual memory exceeds the previous maximum. This option is 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). π This option is disabled by default.opt.prof_leak(bool)r-[--enable-prof]β leak reporting, on/off. If it's 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 it does nothing. This option is 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, it also enablesopt.prof_leak. Works only when combined withopt.prof_final, otherwise it does nothing. This option is 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. π
Okay bestie, so real talk: there's a huge divergence in how different implementations handle this case. A lot of them just do the "free" thing. And that can open up security vulnerabilities, because a NULL return value means failure, and the passed-in pointer stays valid (per POSIX and C11). The "alloc" option is safe, but it can cause leaks in programs that expect the common behavior. So if your program is meant to be portable and leak-free, you literally cannot assume either behavior, and you must therefore never call realloc with a size of 0. The "abort" option lets you test this behavior. π
thread.arena (unsigned) rw
Get or set the arena that the calling thread is vibing with. If that arena wasn't initialized yet (peep the arena.i.initialized mallctl), it'll get auto-initialized as a side effect of calling this interface. No extra steps, bestie. β¨
thread.allocated (uint64_t) r- [--enable-stats]
Get the total number of bytes ever allocated by the calling thread. Heads up: this counter can wrap around, so 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 that thread.allocated returns. This is a whole hack to avoid the overhead of calling mallctl*() over and over. But bestie, do NOT modify the underlying counter. That's a no-no. π«
thread.deallocated (uint64_t) r- [--enable-stats]
Get the total number of bytes ever deallocated by the calling thread. Same wrap-around warning as above: it can wrap, so interpret carefully. π
thread.deallocatedp (uint64_t *) r- [--enable-stats]
Get a pointer to what thread.deallocated returns. Again, saves you from repeated mallctl*() calls. And again, don't touch the underlying counter. π
thread.peak.read (uint64_t) r- [--enable-stats]
Get an approximation of the peak net bytes: that's max(allocated β deallocated) for the calling thread since the last thread.peak.reset, or since the thread was born if it never called thread.peak.reset. No promises on how good the approximation is, but jemalloc is currently trying to keep it accurate within one hundred kilobytes. π―
thread.peak.reset (void) -- [--enable-stats]
Resets the net-bytes-allocated counter for the calling thread to zero. This changes what thread.peak.read says next, but it does NOT change thread.allocated or thread.deallocated. Those stay exactly as they were. π
thread.tcache.enabled (bool) rw
Turn the calling thread's tcache on or off. If you disable it, the tcache gets implicitly flushed as a side effect (peep thread.tcache.flush for the deets). π§Ή
thread.tcache.flush (void) --
Flush the calling thread's tcache. This releases all cached objects and internal data structures tied to that tcache. Usually you don't need to call this, because automatic periodic incremental GC is a thing, and the thread cache gets discarded when the thread exits. BUT: GC is triggered by allocation activity, so if a thread just stops allocating/deallocating, it can hold onto its cache forever. In that case, manual flushing is lowkey useful. π§Ό
thread.prof.name (const char *) r- or -w [--enable-prof]
Get or set the cute descriptive name for the calling thread in memory profile dumps. An internal copy of the name is made, so you don't have to keep your input string alive after this call. But if you're reading, copy the output string if you need it non-ephemerally, because implementation details can cause asynchronous string deallocation. Also, each call can only read OR write β simultaneous read/write is not supported because of string lifetime limits. And the name must be nil-terminated and only contain characters from the sets recognized by isgraph(3) and isblank(3). βοΈ
thread.prof.active (bool) rw [--enable-prof]
Control whether sampling is currently active for the calling thread. This is an extra activation switch on top of prof.active; both have to be active for the calling thread to actually sample. This flag is enabled by default. ποΈ
thread.idle (void) --
Hint to jemalloc that the calling thread is about to be idle for a nontrivial amount of time (like seconds, not milliseconds), and that doing some cleanup might be a good idea. No guarantees on exactly what cleanup happens; currently it flushes the caller's tcache and may (based on some heuristic) purge its associated arena.
This is NOT a general-purpose background activity mechanism, and threads should not wake up multiple times just to call it. Instead, if a thread is waiting for a task, do a timed wait first, call thread.idle if no task shows up in the timeout, then do an untimed wait. For an actual background activity mechanism, see background_thread. π΄
tcache.create (unsigned) r-
Create an explicit tcache and get back an identifier you can pass to the MALLOCX_TCACHE(tc) macro to use that specific cache instead of the automatically managed one. Each explicit cache can only be used by one thread at a time β you, the application, have to make sure that constraint holds.
Also, if the space you gave for storing the tcache identifier isn't exactly sizeof(unsigned), then no tcache gets created, no data is written to oldp, and *oldlenp is set to 0. π
tcache.flush (unsigned) -w
Flush the specified tcache. Same considerations as thread.tcache.flush, except this tcache will NEVER be automatically discarded. So you're in charge, bestie. π§½
tcache.destroy (unsigned) -w
Flush the specified tcache and free up its identifier so it can be used in a future tcache creation. Recycle, slay. β»οΈ
arena.<i>.initialized (bool) r-
Check whether arena <i>'s statistics are initialized β meaning the arena was initialized before the current epoch. You can also nominally use this to query whether the merged stats for MALLCTL_ARENAS_ALL are initialized (spoiler: always true). β
arena.<i>.decay (void) --
Trigger decay-based purging of unused dirty/muzzy pages for arena <i>, or for all arenas if <i> is MALLCTL_ARENAS_ALL. How much gets purged depends on the current time; check 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> is MALLCTL_ARENAS_ALL. Clean sweep. π§Ή
arena.<i>.reset (void) --
Discard every extant allocation in the arena. This only works with arenas explicitly created via arenas.create. After this, none of the arena's discarded/cached allocations may be accessed β like, at all. And as part of that requirement, all thread caches that were used to allocate/deallocate with this arena must be flushed beforehand. Don't skip that step. β οΈ
arena.<i>.destroy (void) --
Destroy the arena. This discards all extant allocations the same way arena.<i>.reset does (same constraints, same side effects), merges the arena stats into the ones at arena index MALLCTL_ARENAS_DESTROYED, then completely drops all metadata for the arena. Future arenas.create calls might recycle that arena index. But heads up: destruction will FAIL if any threads are still associated with the arena because of thread.arena calls. So untangle that first. π
arena.<i>.dss (const char *) rw
Set the precedence of dss allocation relative to mmap allocation for arena <i>, or for all arenas if <i> is MALLCTL_ARENAS_ALL. Check opt.dss for the supported settings. πΊοΈ
arena.<i>.dirty_decay_ms (ssize_t) rw
Current per-arena approximate time in milliseconds from when a set of unused dirty pages is created until an equivalent set gets purged and/or reused. Every time you set this, all currently unused dirty pages are considered fully decayed β which means immediate purging of all unused dirty pages, unless the decay time is -1 (aka purging disabled). See opt.dirty_decay_ms for more. β±οΈ
arena.<i>.muzzy_decay_ms (ssize_t) rw
Same as above but for muzzy pages. Current per-arena approximate time in milliseconds from creation of a set of unused muzzy pages until an equivalent set gets purged and/or reused. Setting it makes all currently unused muzzy pages fully decayed, so immediate purging happens unless decay time is -1 (purging disabled). See opt.muzzy_decay_ms for more. β³
arena.<i>.retain_grow_limit (size_t) rw
Maximum size to grow the retained region. Only relevant when opt.retain is enabled. This controls the max increment for expanding virtual memory, or allocation through arena.<i>extent_hooks. Especially useful if your custom extent hooks reserve physical memory (like 1G huge pages) and you want to control the allocation hook's input size. The default is no limit. π
arena.<i>.extent_hooks (extent_hooks_t *) rw
Get or set the extent management hook functions for arena <i>. Those functions have to 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 control allocation for arenas explicitly created via arenas.create so that all extents come from an application-supplied extent allocator (just specify your custom extent hook functions when creating the arena). BUT: for automatically created arenas, the API guarantees may be relaxed β hooks set there may be called in a "best effort" fashion, and there may be extents created before the application even gets a chance to take over extent allocation. So, y'know, manage your expectations. πͺ
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 the extent_hooks_t struct is literally just a lil bundle of function pointers, and we're gonna walk through every single one of them below, no skipping bestie π jemalloc uses these functions to manage extent lifetime, which kicks off with allocation of mapped committed memory, and in the simplest case that's followed by deallocation. but β and this is the tea β β there are performance and platform reasons to hold onto extents for later reuse instead of yeeting them immediately. Cleanup attempts cascade from deallocation β‘οΈ decommit β‘οΈ forced purging β‘οΈ lazy purging, which gives the extent management functions a chance to reject the most permanent cleanup operations in favor of the less permanent (and usually less costly) ones. so polite of them honestly π«Ά
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 bestie, listen β 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. like, it lives there forever now. no takebacks π
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 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, as well as setting *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. And heads up: 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, that means 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. so it's not gone, it's just β¨on iceβ¨
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. no opt-out here, it's giving final boss energy π€ This function may be called to destroy retained extents during arena destruction (see arena.<i>.destroy).
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 β like, oops, out of RAM bestie π
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. again: not deleted, just vibing in the background πΈ
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 π
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. it said "no thank you, I'm one entity" π
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. not every friendship is meant to merge, and that's okay π
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. π
β οΈ careful bestie: 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. so like, measure twice, allocate once π
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 to the file you name, bestie πΎ and if you passNULLinstead of a filename it'll pick one for you using the pattern<prefix>.<pid>.<seq>.m<mseq>.heap, where<prefix>is whatever you set with theopt.prof_prefixandprof.prefixoptions. slay.prof.prefix(const char *)-w[--enable-prof] β sets the filename prefix for profile dumps. the default lives inopt.prof_prefixif you're curious. super handy for telling your dumps apart, like when you've got forked processes running around doing their own thing πprof.gdump(bool)rw[--enable-prof] β when you turn this on, it triggers a memory profile dump every single time the total virtual memory exceeds the previous maximum π profiles get dumped into files named like<prefix>.<pid>.<seq>.u<useq>.heap, where<prefix>comes from theopt.prof_prefixandprof.prefixoptions. periodt.prof.reset(size_t)-w[--enable-prof] β resets all memory profile statistics, and optionally updates the sample rate too (peepopt.lg_prof_sampleandprof.lg_samplefor that part) πprof.lg_sample(size_t)r-[--enable-prof] β reads out the current sample rate. seeopt.lg_prof_samplefor the deets β¨prof.interval(uint64_t)r-[--enable-prof] β the average number of bytes allocated between interval-based profile dumps.opt.lg_prof_intervalhas extra info if you want it π«Άstats.allocated(size_t)r-[--enable-stats] β total bytes allocated by the application, fr fr.stats.active(size_t)r-[--enable-stats] β total bytes sitting in active pages allocated by the application. it's a multiple of the page size, and it's greater than or equal tostats.allocated. note that it does not includestats.arenas.<i>.pdirty,stats.arenas.<i>.pmuzzy, or pages that are entirely devoted to allocator metadata πstats.metadata(size_t)r-[--enable-stats] β total bytes dedicated to metadata, which is made up 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 (the kind you enable withopt.metadata_thp) is not counted here π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. it's a max and not an exact number because pages might not actually be physically resident if they're demand-zeroed virtual memory that nobody's touched yet. multiple of the page size, and bigger thanstats.activeπ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 β not even ones holding unused dirty pages β which means there's no strict ordering between this andstats.residentπstats.retained(size_t)r-[--enable-stats] β total bytes in virtual memory mappings that got retained instead of being handed back to the operating system via e.g.munmap(2)or similar. retained virtual memory is typically untouched, decommitted, or purged, so it has no strongly associated physical memory (see extent hooks for details). retained memory is excluded from mapped memory statistics, likestats.mappedπstats.zero_reallocs(size_t)r-[--enable-stats] β how many timesrealloc()got called with a non-NULLpointer argument and a0size argument. > heads up bestie, this is a fundamentally unsafe pattern in portable programs;opt.zero_reallochas the details. not cute, don't do it πstats.background_thread.num_threads(size_t)r-[--enable-stats] β how many background threads are running right now π§΅stats.background_thread.num_runs(uint64_t)r-[--enable-stats] β total number of runs from all the background threads combined.stats.background_thread.run_interval(uint64_t)r-[--enable-stats] β average run interval of background threads, in nanoseconds β±οΈstats.mutexes.ctl.{counter}(counter specific type)r-[--enable-stats] β stats on the ctl mutex (global scope; mallctl related).{counter}is one of these counters below, bestie: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. acquiring through spin generally means the contention was lightweight and didn't cause context switches β¨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 to grab the mutex. this event generally implies higher cost / longer delay, and you should investigate if it happens a lot π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. also not considering spin-acquired cases.num_owner_switch(uint64_t): number of times the current mutex owner is different 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 π
stats.mutexes.background_thread.{counter}(counter specific type)r-[--enable-stats] β stats 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] β stats 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] β stats 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] β stats 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, including global mutexes, arena mutexes and bin mutexes. fresh start, iconic π§Ό
stats.arenas.<i>.* β the per-arena zone π¦
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 the creation of a set of unused dirty pages 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 the creation of a set of unused muzzy pages 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 this 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 since malloc initialization β±οΈ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 hasn't been called.opt.dirty_decay_mshas a description of dirty pages if you need it π«§stats.arenas.<i>.pmuzzy(size_t)r-β number of pages within unused extents that are muzzy.opt.muzzy_decay_msdescribes muzzy pages π«οΈ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 the 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 are different from application-originated allocations because they're for internal use, and because they get omitted from heap profiles π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. it's a maximum rather than precise because pages might not actually be physically resident if they correspond to demand-zeroed virtual memory that hasn't been touched yet. 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 πstats.arenas.<i>.small.nmalloc(uint64_t)r-[--enable-stats]β how many times, total, someone asked this arena's bins for a small allocation. Whether that was to top up the relevant tcache (whenopt.tcacheis on) or to just hand out the allocation straight up β doesn't matter, it counts either way. β¨stats.arenas.<i>.small.ndalloc(uint64_t)r-[--enable-stats]β how many times, total, a small allocation got handed back to this arena's bins. Same deal: whether it was flushing the relevant tcache (ifopt.tcacheis enabled) or deallocating directly, it's in here. πstats.arenas.<i>.small.nrequests(uint64_t)r-[--enable-stats]β total allocation requests that got satisfied across all bin size classes. every single one, bestie.stats.arenas.<i>.small.nfills(uint64_t)r-[--enable-stats]β total tcache fills done by all the small size classes combined. πstats.arenas.<i>.small.nflushes(uint64_t)r-[--enable-stats]β total tcache flushes done by all the small size classes combined. π¦stats.arenas.<i>.large.allocated(size_t)r-[--enable-stats]β how many bytes are currently allocated by large objects. right now. live. πstats.arenas.<i>.large.nmalloc(uint64_t)r-[--enable-stats]β total times a large extent got allocated out of the arena. Was it to fill the relevant tcache (ifopt.tcacheis on and the size class falls within the range being cached)? or to directly satisfy an allocation? Either way it's counted. π«Άstats.arenas.<i>.large.ndalloc(uint64_t)r-[--enable-stats]β total times a large extent came back to the arena. Again: flushing the relevant tcache (ifopt.tcacheis enabled and the size class is within the range being cached), or a direct deallocation β both land here. πΈstats.arenas.<i>.large.nrequests(uint64_t)r-[--enable-stats]β total allocation requests satisfied by every large size class. β¨stats.arenas.<i>.large.nfills(uint64_t)r-[--enable-stats]β total tcache fills by all large size classes. π₯stats.arenas.<i>.large.nflushes(uint64_t)r-[--enable-stats]β total tcache flushes by all large size classes. πstats.arenas.<i>.bins.<j>.nmalloc(uint64_t)r-[--enable-stats]β total times a bin region of that corresponding size class got allocated from the arena β whether to fill the relevant tcache (ifopt.tcacheis enabled) or to just directly satisfy an allocation request. πstats.arenas.<i>.bins.<j>.ndalloc(uint64_t)r-[--enable-stats]β total times a bin region of that size class got returned to the arena β whether flushing the relevant tcache (ifopt.tcacheis enabled) or directly deallocating. πstats.arenas.<i>.bins.<j>.nrequests(uint64_t)r-[--enable-stats]β total allocation requests satisfied by bin regions of that corresponding size class. πstats.arenas.<i>.bins.<j>.curregs(size_t)r-[--enable-stats]β how many regions exist for this size class right now. πstats.arenas.<i>.bins.<j>.nfills(uint64_t)r-β total tcache fills. (no[--enable-stats]tag on this one, she's always on β¨)stats.arenas.<i>.bins.<j>.nflushes(uint64_t)r-β total tcache flushes. πstats.arenas.<i>.bins.<j>.nslabs(uint64_t)r-[--enable-stats]β total slabs created, cumulative. π§stats.arenas.<i>.bins.<j>.nreslabs(uint64_t)r-[--enable-stats]β total times the current slab you're allocating from switched to a different one. πstats.arenas.<i>.bins.<j>.curslabs(size_t)r-[--enable-stats]β current number of slabs. as in, right this second.stats.arenas.<i>.bins.<j>.nonfull_slabs(size_t)r-[--enable-stats]β current number of nonfull slabs. π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]β how many extents of the given type live in this arena, in the bucket matching 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]β the sum of bytes managed by extents of the given type in this arena, in the bucket matching page size index<j>. Extent type is one ofdirty,muzzy, orretained. π¦stats.arenas.<i>.lextents.<j>.nmalloc(uint64_t)r-[--enable-stats]β total times a large extent of that size class got 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>.lextents.<j>.ndalloc(uint64_t)r-[--enable-stats]β total times a large extent of that size class got returned to the arena β whether flushing the relevant tcache (ifopt.tcacheis enabled and the size class is within the range being cached), or directly deallocating otherwise. πΈstats.arenas.<i>.lextents.<j>.nrequests(uint64_t)r-[--enable-stats]β total allocation requests satisfied by large extents of that 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. β¨
πΈ HEAP PROFILE FORMAT πΈ
okay so storytime: heap profiling was originally designed to be compatible with the pprof command that ships as part of the gperftools package<sup>[3]</sup>... but then per-thread heap profiling entered the chat and required a whole different heap profile format. The jeprof command is derived from pprof, with enhancements to support the heap profile format described right here. π
In the following hypothetical heap profile, [...] indicates elision for the sake of compactness. (we're keeping it cute and short, bestie)
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>
π DEBUGGING MALLOC PROBLEMS π
When you're 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 it's configured like that, jemalloc brings a whole wide variety of run-time assertions that catch application errors β think double-free, write-after-free, etc. She's basically a bodyguard. π
Programs often accidentally depend on "uninitialized" memory actually being filled with zero bytes. (not me gaslighting myself into thinking malloc zeroes things π) 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. π¦
Heads up bestie: this implementation does not provide much detail about the problems it detects, because the performance impact for storing such information would be prohibitive. π
π DIAGNOSTIC MESSAGES π
okay bestie, so if any of the memory allocation/deallocation functions detect an error or warning condition, a message gets printed to the file descriptor STDERR_FILENO. Errors? They make the process dump core. And if the opt.abort option is set, most warnings are treated as errors too. π
The malloc_message variable lets the programmer override the function that emits the text strings forming the errors and warnings, if for some reason the STDERR_FILENO file descriptor is not suitable for this. malloc_message() takes the cbopaque pointer argument that is NULL unless overridden by the arguments in a call to malloc_stats_print(), followed by a string pointer.
π¨ Please note: doing anything which tries to allocate memory in this function is likely to result in a crash or deadlock. no cap, do not do it.
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 *).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.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. π«Ά
The free() function returns no value.
π 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βnewpis notNULL, andnewlenis 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βnameormibspecifies 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";
π¦ SEE ALSO π¦
madvise(2), mmap(2), sbrk(2), utrace(2), alloca(3), atexit(3), getpagesize(3)
β 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β).
π AUTHOR π
Jason Evans
π NOTES π
- jemalloc website β
http://jemalloc.net/ - JSON format β
http://www.json.org/ - gperftools package β
http://code.google.com/p/gperftools/
and that's the whole vibe, bestie π«Άβ¨ this iconic manual page is from jemalloc 5.3.0-0-g54eaed1d8b56, dated 05/06/2022, and it's literally JEMALLOC(3) β periodt π
π
π¦ where this page comes from
- alpine 3.22: jemalloc-doc 5.3.0-r6 Β· BSD-2-Clause
- alpine 3.23: jemalloc-doc 5.3.0-r6 Β· BSD-2-Clause
- alpine 3.24: jemalloc-doc 5.3.0-r6 Β· BSD-2-Clause
- epel 10: jemalloc-devel 5.3.0-10.el10_1 Β· LicenseRef-Callaway-BSD
- fedora 43: jemalloc-devel 5.3.0-13.fc43 Β· LicenseRef-Callaway-BSD
- fedora 43: jemalloc-devel 5.3.0-13.fc43 Β· LicenseRef-Callaway-BSD
- fedora 44: jemalloc-devel 5.3.0-14.fc44 Β· LicenseRef-Callaway-BSD
- fedora 44: jemalloc-devel 5.3.0-14.fc44 Β· LicenseRef-Callaway-BSD
- opensuse-leap 16.0: jemalloc 5.3.0-160000.2.2 Β· BSD-2-Clause
- opensuse-leap 16.0: jemalloc 5.3.0-160000.2.2 Β· BSD-2-Clause
- opensuse-leap 16.0: jemalloc 5.3.0-160000.2.2 Β· BSD-2-Clause
- opensuse-leap 16.0: jemalloc 5.3.0-160000.2.2 Β· BSD-2-Clause
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms