Por que o jemalloc importa: alocação de memória em escala
O jemalloc gerencia a alocação de memória de alguns dos maiores sistemas do mundo. Veja como ele reduz a fragmentação e por que a Meta aposta nele.

Toda vez que seu programa chama malloc(), algo precisa decidir qual pedaço da memória virtual devolver. Essa decisão, trivial em um programa pequeno, vira um problema de engenharia enorme em escala. Um alocador ruim fragmenta a memória, desperdiça RAM, cria disputa por locks entre threads e causa picos de latência quando o SO precisa recuperar páginas. Um bom alocador não faz nada disso. O jemalloc é um bom alocador, e a Meta acaba de renovar seu compromisso com ele porque, na escala deles, a diferença entre um alocador bom e um medíocre são bilhões de dólares em custos de hardware.
A maioria dos desenvolvedores nunca pensa em alocação de memória. Eles chamam new ou malloc e recebem um ponteiro de volta. Mas na escala da Meta, com bilhões de requisições diárias, milhões de servidores e petabytes de RAM, o comportamento do alocador tem impacto direto e mensurável na eficiência de hardware, na latência de cauda e no custo operacional.
O que um alocador de memória realmente faz
O alocador de memória fica entre o seu programa e o sistema operacional. O SO fornece memória em grandes blocos (páginas, normalmente de 4KB ou 2MB). Já o seu programa precisa de memória em pedaços pequenos e de tamanho variável (uma string de 24 bytes aqui, um buffer de 4096 bytes ali). O trabalho do alocador é fatiar as páginas fornecidas pelo SO nos pedaços que o programa precisa e reciclar os pedaços liberados para alocações futuras.
A abordagem ingênua, que é pedir uma nova página ao SO para cada alocação e devolvê-la na liberação, é catastroficamente lenta. Chamadas de sistema têm overhead. As páginas são bem maiores que a maioria das alocações. Você usaria 4KB de memória para uma string de 24 bytes.
Alocadores reais mantêm um pool de memória e fazem sub-alocações a partir dele. Os desafios são: minimizar a fragmentação (lacunas entre alocações pequenas demais para serem usadas), minimizar a disputa por locks (várias threads alocando ao mesmo tempo) e minimizar o overhead (os metadados por alocação devem ser pequenos em relação à própria alocação).
Como o jemalloc funciona
O jemalloc (criado por Jason Evans, daí o 'je') foi originalmente desenvolvido para o FreeBSD e depois adotado pela Meta (na época, Facebook) como alocador padrão em seus serviços em C e C++. Seu design enfrenta os três principais desafios com técnicas específicas.
Caches por thread eliminam a disputa. Cada thread tem seu próprio cache de alocações pequenas. Quando uma thread chama malloc() para um objeto pequeno, a alocação é atendida inteiramente pelo cache local da thread, sem locks, sem operações atômicas e sem disputa. Só quando o cache da thread se esgota é que ela vai à arena compartilhada para reabastecê-lo.
jemalloc allocation flow:
Thread calls malloc(64)
↓
Check thread cache for 64-byte size class
→ Hit: return cached chunk (no lock, no contention)
→ Miss: refill from arena
↓
Arena (shared, but per-thread affinity)
→ Find a partially-full slab for 64-byte objects
→ Carve out a chunk
→ Return to thread cache
↓
Return chunk to caller
Thread caches handle 95%+ of allocations without any locking.
Size classes reduzem a fragmentação. Em vez de alocar exatamente o número de bytes pedido, o jemalloc arredonda para a size class mais próxima. As size classes são escolhidas com cuidado: 8, 16, 32, 48, 64, 80, 96, 112, 128, 160, 192, 224, 256 e assim por diante, com espaçamento que cresce conforme o tamanho aumenta. Isso significa que uma alocação de 50 bytes recebe um pedaço de 64 bytes (23% de desperdício). Parece ruim, mas na prática é bom: como todos os pedaços de 64 bytes são intercambiáveis, não há fragmentação externa dentro de uma size class.
Slabs organizam alocações de mesmo tamanho. Cada slab é uma região contígua de memória dividida em pedaços de uma mesma size class. Um slab para objetos de 64 bytes contém apenas pedaços de 64 bytes. Isso elimina o pior tipo de fragmentação, aquele em que a memória liberada não pode ser reutilizada porque está espremida entre alocações ativas de tamanhos diferentes.
O problema da fragmentação
A fragmentação de memória é o assassino silencioso de serviços de longa duração. Um servidor recém-iniciado usa a memória com eficiência, pois as alocações estão bem compactadas. Depois de dias ou semanas de alocações e liberações misturadas, o heap vira queijo suíço: muitas lacunas livres pequenas entre alocações ativas. A memória livre total pode ser de 2GB, mas a maior região contígua livre tem apenas 64KB.
A fragmentação externa (lacunas inutilizáveis entre alocações) e a interna (espaço desperdiçado dentro das alocações por causa do arredondamento) importam, mas a externa é pior. A interna é limitada pelo espaçamento das size classes: no pior caso, você desperdiça cerca de 25% por alocação. A externa não tem limite e cresce com o tempo.
A abordagem baseada em slabs do jemalloc praticamente elimina a fragmentação externa para alocações pequenas, que são a grande maioria. Como todos os objetos de um slab têm o mesmo tamanho, liberar um deles cria um buraco exatamente do tamanho certo para a próxima alocação daquela size class. Não sobram lacunas inutilizáveis.
Para alocações grandes (tipicamente acima de 14KB), o jemalloc usa outra estratégia: elas recebem páginas próprias, e as páginas liberadas podem ser devolvidas ao SO ou reutilizadas em outras size classes. É aqui que a fragmentação ainda pode acontecer, mas alocações grandes são relativamente raras.
jemalloc vs. glibc malloc vs. tcmalloc
Os três principais alocadores do ecossistema Linux fazem trade-offs diferentes.
- glibc malloc (ptmalloc2) é o padrão na maioria dos sistemas Linux. Ele usa arenas para escalar com threads, mas tem menos size classes que o jemalloc, o que leva a mais fragmentação em serviços de longa duração. Sua principal vantagem é ser o padrão: não precisa de configuração.
- tcmalloc (Google) é um malloc com cache por thread, originalmente desenvolvido para os serviços em C++ do Google. Tem um cache thread-local excelente e baixo overhead. É bem indicado para cargas com muitas alocações de vida curta e menos indicado para cargas com alta pressão de memória, onde a fragmentação importa.
- jemalloc otimiza para baixa fragmentação e comportamento previsível sob carga sustentada. Usa size classes e gerenciamento de slabs mais sofisticados que os outros. O trade-off: um overhead por alocação um pouco maior (mais metadados), em troca de melhor eficiência de memória ao longo do tempo.
A escolha certa depende da sua carga de trabalho. Para processos de vida curta, os três se saem de forma parecida. Para servidores de longa duração com padrões de alocação mistos (servidores web, bancos de dados, caches), a resistência do jemalloc à fragmentação costuma vencer: você usa de 10 a 30% menos RAM para a mesma carga em comparação com o glibc malloc, o que, em escala, se traduz diretamente em economia de hardware.
Por que a Meta se importa
Na escala da Meta, uma redução de 10% no uso de memória nos seus serviços em C++ economiza milhões de dólares em hardware. Não por ano, e sim por mês. Quando você opera milhões de servidores, cada um rodando serviços que alocam e liberam memória bilhões de vezes por dia, a eficiência do alocador vira uma linha no orçamento.
O investimento renovado da Meta no jemalloc se concentra em algumas áreas: melhor suporte a huge pages (reduzindo misses no TLB em sistemas com muita memória), melhor devolução de memória ao SO (reduzindo a memória residente quando a carga diminui) e ferramentas de profiling melhores (entender onde a memória está sendo usada e onde está sendo desperdiçada).
O aspecto de profiling é particularmente interessante. O jemalloc inclui profiling de heap embutido: você pode pedir que ele amostre alocações e produza um perfil mostrando onde a memória foi alocada, quanto está ativa versus liberada e o quão fragmentado está o heap. Esse profiling tem overhead próximo de zero em produção, o que torna viável rodá-lo continuamente em servidores de produção.
Usando o jemalloc nos seus projetos
Trocar para o jemalloc costuma ser trivial em programas C/C++. No Linux, você pode linkar diretamente com ele ou usar LD_PRELOAD para injetá-lo em tempo de execução, sem nenhuma mudança de código.
# Install jemalloc
sudo apt install libjemalloc-dev # Debian/Ubuntu
brew install jemalloc # macOS
# Use LD_PRELOAD — works with any program, no recompilation
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so ./my_server
# Or link at compile time
gcc -o my_server my_server.c -ljemalloc
# Enable profiling
export MALLOC_CONF="prof:true,prof_prefix:jeprof"
./my_server
# Then analyze: jeprof --svg ./my_server jeprof.*.heap > heap.svg
Vários projetos de grande visibilidade usam o jemalloc por padrão: Redis, Rust (usa o jemalloc como alocador padrão em algumas plataformas), Firefox (o jemalloc foi originalmente desenvolvido para o FreeBSD, plataforma que o Firefox também suportava) e muitos motores de jogos.
Para linguagens com gerenciamento de memória automático (Python, Java, Go), o runtime da linguagem cuida da alocação e o jemalloc não se aplica diretamente. Mas os conceitos (cache local por thread, size classes, alocação por slabs) aparecem em praticamente todo runtime moderno e em todo coletor de lixo. O alocador do runtime do Go, por exemplo, usa um design inspirado no tcmalloc, com caches por P (processador) e size classes.
A infraestrutura invisível
Alocação de memória é infraestrutura invisível quando funciona e catastrófica quando não funciona. Um servidor que vaza memória aos poucos por causa de fragmentação acaba sofrendo um OOM-kill, e a causa não aparece em nenhum log da aplicação, pois ela está abaixo da camada da aplicação.
Para a maioria das aplicações, o alocador padrão é suficiente. Mas se você roda servidores de longa duração, enfrenta crescimento de memória sem explicação ou opera numa escala em que a eficiência de hardware importa, entender o seu alocador (e possivelmente trocar por um melhor) é uma das mudanças de infraestrutura com maior alavancagem que você pode fazer. O jemalloc não é mágica. É engenharia: design cuidadoso de estruturas de dados, trade-offs bem informados e atenção incansável aos detalhes que importam em escala.


