Mengapa jemalloc Penting: Alokasi Memori dalam Skala Besar
jemalloc mengatur alokasi memori untuk sistem terbesar di dunia. Simak cara ia mengurangi fragmentasi dan alasan Meta terus berinvestasi padanya.

Setiap kali programmu memanggil malloc(), ada yang harus memutuskan potongan memori virtual mana yang dikembalikan. Keputusan ini, yang sepele untuk program kecil, menjadi masalah rekayasa besar dalam skala besar. Alokator yang buruk memecah memori (fragmentasi), membuang RAM, menciptakan lock contention antar thread, dan memicu lonjakan latensi saat OS harus merebut kembali halaman memori. Alokator yang baik tidak melakukan semua itu. jemalloc adalah alokator yang baik, dan Meta baru saja memperbarui komitmennya padanya karena di skala mereka, selisih antara alokator bagus dan biasa-biasa saja berarti miliaran dolar biaya perangkat keras.
Kebanyakan developer tidak pernah memikirkan alokasi memori. Mereka memanggil new atau malloc dan langsung mendapatkan pointer. Tapi di skala Meta, dengan miliaran request per hari, jutaan server, dan petabyte RAM, perilaku alokator berdampak langsung dan terukur pada efisiensi perangkat keras, tail latency, dan biaya operasional.
Apa Sebenarnya yang Dilakukan Alokator Memori
Alokator memori berada di antara programmu dan sistem operasi. OS menyediakan memori dalam potongan besar (halaman, biasanya 4KB atau 2MB). Programmu membutuhkan memori dalam potongan kecil dengan ukuran bervariasi (string 24 byte di sini, buffer 4096 byte di sana). Tugas alokator adalah memotong halaman dari OS menjadi potongan yang dibutuhkan program, dan mendaur ulang potongan yang sudah dibebaskan untuk alokasi berikutnya.
Pendekatan naif, yaitu meminta halaman baru dari OS untuk setiap alokasi lalu mengembalikannya saat dibebaskan, sangat lambat. System call punya overhead. Halaman jauh lebih besar dari sebagian besar alokasi. Kamu bisa memakai 4KB memori hanya untuk string 24 byte.
Alokator sungguhan memelihara kumpulan memori dan melakukan sub-alokasi dari sana. Tantangannya: meminimalkan fragmentasi (celah antar alokasi yang terlalu kecil untuk dipakai), meminimalkan lock contention (banyak thread mengalokasi bersamaan), dan meminimalkan overhead (metadata per alokasi harus kecil dibanding alokasinya sendiri).
Cara Kerja jemalloc
jemalloc (dibuat oleh Jason Evans, makanya namanya 'je') awalnya dikembangkan untuk FreeBSD lalu diadopsi Meta (saat itu Facebook) sebagai alokator default di seluruh layanan C dan C++ mereka. Desainnya menjawab tiga tantangan utama tadi dengan teknik-teknik spesifik.
Thread cache menghilangkan contention. Setiap thread punya cache sendiri untuk alokasi kecil. Saat thread memanggil malloc() untuk objek kecil, alokasinya dilayani sepenuhnya dari cache lokal thread tanpa lock, tanpa operasi atomik, dan tanpa contention. Baru ketika cache thread habis, ia mengambil dari arena bersama untuk diisi ulang.
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 class mengurangi fragmentasi. Alih-alih mengalokasi jumlah byte yang persis diminta, jemalloc membulatkan ke size class terdekat. Size class dipilih dengan cermat: 8, 16, 32, 48, 64, 80, 96, 112, 128, 160, 192, 224, 256, dan seterusnya, dengan jarak yang makin lebar seiring ukuran naik. Artinya, alokasi 50 byte mendapat potongan 64 byte (terbuang 23%). Kedengarannya buruk, padahal sebenarnya bagus, karena semua potongan 64 byte bisa saling menggantikan sehingga tidak ada fragmentasi eksternal di dalam satu size class.
Slab mengelompokkan alokasi berukuran sama. Setiap slab adalah region memori yang berurutan dan dibagi menjadi potongan dengan size class yang sama. Slab untuk objek 64 byte isinya hanya potongan 64 byte. Ini menghilangkan jenis fragmentasi terburuk, yaitu memori yang sudah dibebaskan tidak bisa dipakai ulang karena terjepit di antara alokasi aktif dengan ukuran berbeda.
Masalah Fragmentasi
Fragmentasi memori adalah pembunuh diam-diam untuk layanan yang berjalan lama. Server yang baru dinyalakan memakai memori dengan efisien karena alokasinya rapat. Setelah berhari-hari atau berminggu-minggu dengan alokasi dan pembebasan yang campur aduk, heap berubah seperti keju Swiss: banyak celah bebas kecil di antara alokasi aktif. Total memori bebasnya mungkin 2GB, tapi area kosong berurutan terbesar hanya 64KB.
Fragmentasi eksternal (celah yang tidak bisa dipakai di antara alokasi) dan fragmentasi internal (ruang terbuang di dalam alokasi akibat pembulatan) sama-sama penting, tapi fragmentasi eksternal lebih parah. Fragmentasi internal dibatasi oleh jarak size class, jadi paling buruk kamu membuang sekitar 25% per alokasi. Fragmentasi eksternal tidak terbatas dan terus membesar seiring waktu.
Pendekatan berbasis slab milik jemalloc sebagian besar menghilangkan fragmentasi eksternal untuk alokasi kecil, yang jumlahnya paling banyak. Karena semua objek di slab berukuran sama, membebaskan satu objek menciptakan lubang yang pas untuk alokasi berikutnya dengan size class yang sama. Tidak ada celah yang tak terpakai.
Untuk alokasi besar (biasanya di atas 14KB), jemalloc memakai strategi berbeda. Alokasi besar mendapat halaman sendiri, dan halaman yang dibebaskan bisa dikembalikan ke OS atau dipakai ulang untuk size class lain. Di sinilah fragmentasi masih bisa terjadi, tapi alokasi besar relatif jarang.
jemalloc vs. glibc malloc vs. tcmalloc
Tiga alokator utama di ekosistem Linux masing-masing punya trade-off yang berbeda.
- glibc malloc (ptmalloc2) adalah default di sebagian besar sistem Linux. Ia memakai arena untuk skalabilitas thread, tapi size class-nya lebih sedikit dibanding jemalloc, sehingga fragmentasinya lebih tinggi di layanan yang berjalan lama. Keunggulan utamanya: ia default, jadi tidak perlu konfigurasi.
- tcmalloc (Google) adalah thread-caching malloc yang awalnya dikembangkan untuk layanan C++ Google. Caching thread-lokalnya sangat bagus dan overhead-nya rendah. Cocok untuk beban kerja dengan banyak alokasi berumur pendek, dan kurang pas untuk beban kerja dengan tekanan memori tinggi di mana fragmentasi jadi penting.
- jemalloc dioptimalkan untuk fragmentasi rendah dan perilaku yang dapat diprediksi di bawah beban berkelanjutan. Ia memakai size class dan manajemen slab yang lebih canggih dibanding yang lain. Trade-off-nya: overhead per alokasi sedikit lebih tinggi (metadata lebih banyak) sebagai imbalan efisiensi memori yang lebih baik seiring waktu.
Pilihannya tergantung pada beban kerjamu. Untuk proses berumur pendek, ketiganya performanya mirip. Untuk server yang berjalan lama dengan pola alokasi campuran (web server, database, cache), ketahanan jemalloc terhadap fragmentasi biasanya menang. Kamu bisa memakai RAM 10-30% lebih sedikit untuk beban kerja yang sama dibanding glibc malloc, dan di skala besar itu langsung berarti penghematan perangkat keras.
Mengapa Meta Peduli
Di skala Meta, penurunan pemakaian memori 10% di seluruh layanan C++ mereka menghemat jutaan dolar biaya perangkat keras. Bukan per tahun, tapi per bulan. Ketika kamu mengoperasikan jutaan server, masing-masing menjalankan layanan yang mengalokasi dan membebaskan memori miliaran kali per hari, efisiensi alokator menjadi salah satu pos di anggaran.
Investasi Meta yang diperbarui di jemalloc berfokus pada beberapa area: dukungan huge page yang lebih baik (mengurangi TLB miss di sistem memori besar), pengembalian memori ke OS yang lebih baik (mengurangi resident memory saat beban turun), dan alat profiling yang lebih baik (memahami di mana memori dipakai dan di mana ia terbuang).
Bagian profiling ini sangat menarik. jemalloc punya heap profiling bawaan. Kamu bisa memintanya men-sample alokasi dan menghasilkan profil yang menunjukkan di mana memori dialokasikan, berapa yang aktif vs. sudah dibebaskan, dan seberapa terfragmentasi heap-nya. Profiling ini hampir tanpa overhead di produksi, sehingga masuk akal untuk dijalankan terus-menerus di server produksi.
Menggunakan jemalloc di Proyekmu
Beralih ke jemalloc biasanya sangat mudah untuk program C/C++. Di Linux, kamu bisa langsung di-link ke library-nya atau memakai LD_PRELOAD untuk menyuntikkannya saat runtime, tanpa perlu mengubah kode.
# 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
Beberapa proyek besar memakai jemalloc secara default: Redis, Rust (memakai jemalloc sebagai alokator default di beberapa platform), Firefox (jemalloc awalnya dikembangkan untuk FreeBSD, yang juga dituju Firefox), dan banyak game engine.
Untuk bahasa dengan memori terkelola (Python, Java, Go), runtime bahasa yang menangani alokasi, jadi jemalloc tidak langsung relevan. Tapi konsepnya, yaitu thread-local caching, size class, dan alokasi slab, muncul di hampir semua runtime modern dan garbage collector-nya. Alokator runtime Go, misalnya, memakai desain yang terinspirasi tcmalloc dengan cache per-P (processor) dan size class.
Infrastruktur yang Tak Terlihat
Alokasi memori adalah infrastruktur yang tak terlihat saat berjalan baik dan jadi bencana saat bermasalah. Server yang perlahan bocor memori akibat fragmentasi pada akhirnya akan kena OOM-kill, dan penyebabnya tidak muncul di log aplikasi mana pun karena ia berada di bawah lapisan aplikasi.
Untuk sebagian besar aplikasi, alokator default sudah cukup. Tapi kalau kamu menjalankan server berumur panjang, mengalami pertumbuhan memori yang tidak bisa dijelaskan, atau beroperasi di skala di mana efisiensi perangkat keras penting, memahami alokatormu (dan mungkin beralih ke yang lebih baik) adalah salah satu perubahan infrastruktur dengan dampak terbesar yang bisa kamu lakukan. jemalloc bukan sihir. Ini rekayasa: desain struktur data yang cermat, trade-off yang terinformasi, dan perhatian tanpa henti pada detail yang penting di skala besar.


