Garbage Collector dituduh bikin aplikasi lemot. Seberapa adil tuduhan itu?

GC sering jadi tersangka utama tiap aplikasi melambat, dan banyak yang percaya dia jalan pakai timer tiap beberapa detik. Dua-duanya nggak sepenuhnya benar. Yuk bedah cara kerja GC dari dalam, plus satu pengecualian yang bikin Discord kesakitan tiap 2 menit. 🤔

Kalau kamu pernah mampir ke forum pemrograman, kamu pasti pernah ketemu kalimat ini: "mau aplikasi kencang? Jauhi bahasa ber-GC kayak Java, Go, C#, Python, atau JavaScript, pakai C, C++, atau Rust aja." Anggapan itu berumur panjang, dan di baliknya ada satu gambaran soal cara kerja GC yang perlu kita periksa.

Ada benarnya, sih: GC emang beneran makan sumber daya, dan itu bukan hal yang bisa dibantah. Cuma, dua hal yang sering dianggap pasti soal GC—dia jalan tiap beberapa detik, dan dia selalu bikin lemot—lebih baik kamu lihat sendiri mekanismenya. Di artikel sebelumnya kita udah lihat Discord dan Cloudflare menghadapi batasan ini dengan mengukur dulu, bukan menebak. Sekarang waktunya ngintip batasan itu dari dalam, yuk. 🙂

Poin Penting

Garbage Collector modern memicu dirinya sendiri lewat ambang alokasi, bukan timer, dan buat sebagian besar aplikasi cara itu justru menguntungkan.

  • Jeda GC di runtime modern seperti Go dan ZGC sudah sub-milidetik dan nggak tumbuh seiring ukuran heap, jadi tuduhan "GC bikin lemot" cuma benar di sistem yang tenggat waktunya mikrodetik.
  • Harga memori dari GC itu nyata tapi bisa diputar: GOGC yang tinggi menghemat CPU dengan tambahan RAM, sementara GOMEMLIMIT yang terlalu ketat justru berujung thrashing.
  • Yang bikin GC jadi masalah bukan kecepatannya, tapi ketidakmampuan menjadwalkan jedanya, jadi selama tenggat aplikasimu longgar tersangkanya salah tangkap.
  • Kasus Instagram menunjukkan sebagian masalah GC bisa dibereskan lewat konfigurasi, dan itu pun cuma masuk akal kalau prosesnya memang berumur pendek.

GC jalan pakai timer?

Sketsa paling gampang buat mbayangin cara kerja GC itu kira-kira begini:

TEXT
while true:
    sleep(5)                        # tunggu 5 detik
    cari_objek_yang_udah_nggak_dipakai()

Tapi bentuk aslinya tuh nggak kayak gitu. Kalau benar ada timer di situ, servermu yang nganggur di tengah malam bakal tetap sibuk memindai memori tiap lima detik: CPU-nya kebakar, tagihan cloud-nya naik, dan yang dibersihkan cuma kekosongan. Untungnya, GC modern nggak sedungu itu. 😅

Yang dipakai hampir semua runtime modern sekarang adalah ambang alokasi, bukan jam. GC cuma bangun kalau memori yang dialokasikan aplikasi nyentuh batas tertentu. Di Go, batas itu diatur lewat environment variable GOGC, yang default-nya 100.

Angka 100 itu artinya begini: GC membiarkan heap tumbuh sampai sekitar dua kali lipat memori hidup. Contohnya nih, kalau setelah satu siklus selesai memori yang masih hidup tinggal 10 MB, GC baru jalan lagi saat heap menyentuh 20 MB. Selama aplikasimu main di bawah angka itu, GC tidur nyenyak. Masuk akal kan?

Angka 100-nya sendiri bisa kamu putar. Turunin ke 50, targetnya jadi sekitar 15 MB—GC lebih sering jalan, tapi RAM lebih hemat. Naikin ke 200, targetnya jadi sekitar 30 MB—GC lebih jarang jalan, tapi RAM lebih boros. Rumus lengkapnya ada di panduan resmi GC Go kalau kamu mau ngitung sendiri, tapi intinya cuma itu.

Jadi jawabannya: nggak. GC nggak dijalankan pakai timer.

Tapi ada juga yang memang berbasis waktu

Di sinilah pertanyaannya jadi lebih menarik: beberapa runtime memang punya pemicu berbasis waktu. Go salah satunya. Dia memaksa GC jalan minimal tiap 2 menit, terlepas dari seberapa lambat heap-nya tumbuh. Bukan gosip forum, itu ada sebagai komentar di kode sumber Go: kalau sudah 2 menit nggak ada koleksi, satu siklus dipaksa jalan.

Kenapa sampai perlu? Karena ambang GOGC cuma berguna kalau aplikasimu rajin mengalokasi. Kalau live heap-nya besar tapi alokasinya lambat—persis seperti Read States di Discord—ambang itu nggak pernah kesentuh, dan timer 2 menit inilah yang akhirnya ambil alih. Setiap kali dia jalan, GC harus memindai seluruh LRU cache yang isinya jutaan user. Dari situ blog Discord sendiri nyimpulin kenapa grafik mereka punya spike yang datang rapi tiap sekitar 2 menit, dan kenapa usaha nge-tuning GC percent gagal total.

Go bukan satu-satunya. V8 di Chrome punya idle-time GC: kerja bersih-bersihnya dititipkan ke idle task yang dijadwalkan browser, bukan ke jadwal tetap. HotSpot malah menyediakan opsi G1PeriodicGCInterval yang memicu siklus GC berkala saat aplikasinya menganggur, biasanya buat balikin memori ke sistem operasi.

Satu catatan jujur sebelum lanjut: kasus Discord itu terjadi di 2020, waktu Go masih di seri 1.8–1.10, dan cara kerja GC-nya sudah banyak berubah sejak itu. Yang nggak berubah: selama heap-mu gede dan isinya banyak pointer, memindainya tetap kerja yang harus dibayar. Perubahan itulah yang mendorong Go 1.19 menambah GOMEMLIMIT, supaya kamu bisa nentuin batas memori absolut, bukan cuma rasio.

Jadi kalau selama ini kamu mbayangin GC punya jadwal rutin, tebakanmu nggak ngawur. Yang bikin beda cuma porsinya: pemicu berbasis waktu itu jaring pengaman atau kerja sampingan, bukan mesin utamanya.

GC itu sebenarnya ngapain di dalam?

GC tuh ibarat tukang bersih-bersih yang nggak keliling tiap jam: dia baru bergerak kalau ada yang memanggilnya. Dan hampir semua GC modern ikut keluarga tracing, dengan pola dasar yang sama: mark, lalu sweep. Biar kebayang, kita bedah dua fase itu satu-satu.

Gimana GC tahu objek mana yang masih hidup?

GC nggak baca memori dari alamat nol secara buta. Dia mulai dari GC roots: variabel lokal di stack, variabel global, dan register CPU. Dari situ dia jalan ngikutin pointer seperti menelusuri pohon. Objek yang kejangkau ditandai hidup. Setelah penelusuran selesai, heap disapu dan semua yang nggak ketandai dikembalikan ke daftar bebas, siap dipakai ulang tanpa minta alokasi baru ke sistem operasi.

Satu detail yang sering kelewat: GC Go itu non-moving. Objeknya nggak digeser-geser di memori. Bahasa lain ada yang menggeser objeknya (compacting) supaya memori lebih rapat, tapi harganya semua pointer harus ikut diperbarui. Java dan V8 memilih jalan tengah dengan generational collector, yang nebak bahwa objek muda lebih cepat mati, jadi nggak semua objek perlu diperiksa sama sering.

Tracing vs menghitung referensi

Ada dua keluarga besar di sini. Tracing dipakai Go, Java, dan V8. Reference counting dipakai Python dan Swift, dengan cara yang beda: tiap objek nyimpen jumlah referensinya, dan begitu angkanya nol, memori langsung dibebaskan. Python bahkan pakai dua-duanya sekaligus, karena refcount sendirian nggak bisa ngurus objek yang saling tunjuk membentuk lingkaran.

Pembagian ini penting buat diingat, soalnya dari sini kebijakan buat nge-tune GC jadi beda-beda: mulai dari GOGC dan GOMEMLIMIT di Go, pilihan collector G1 sampai ZGC di JVM, sampai pilihan buat mematikan sebagian GC di Python.

Terus biaya performanya di mana?

Kalau algoritmanya cuma jalanin pointer, apa yang sebenarnya kita bayar? Tiga hal ini:

  • Jeda singkat di peralihan fase. Di Go, aplikasi cuma benar-benar berhenti (stop-the-world) sebentar aja di transisi antara mark dan sweep, dan panjangnya sengaja dibikin nggak tumbuh seiring ukuran heap. Di JVM, ZGC malah sudah jadi fitur produksi sejak JDK 15 dengan jeda sub-milidetik, bahkan di heap yang ukurannya bukan main.
  • Porsi CPU yang disedot GC. Fase mark ngambil sekitar 25% CPU, dan itu artinya thread aplikasimu rebutan prosesor. Yang paling bikin kesel: kalau alokasimu cepat, goroutine-mu sendiri ikut ditarik buat bantu GC (GC assist).
  • Overhead memori. GOGC=100 artinya kamu nyiapin ruang sekitar dua kali lipat memori hidup. Naikin GOGC, GC jadi jarang jalan dan CPU lebih lega, tapi RAM lebih boros. Pasang GOMEMLIMIT terlalu ketat, kamu dapat masalah baru bernama thrashing: GC jalan terus-terusan, dan aplikasi bisa melambat sampai dua kali lipat.

Ada juga efek samping yang lebih susah diukur: metadata GC ikut memenuhi cache CPU, jadi data logika bisnismu terdepak ke RAM yang latensinya jauh lebih tinggi. Ini bukan angka yang keluar dari halaman dokumentasi mana pun, tapi masuk akal, dan Instagram sendiri nyebut soal naiknya cache hit ratio sebagai salah satu alasan mereka mematikan GC di Python-nya.

Kapan GC beneran jadi masalah?

Di sinilah keputusannya bisa dipertanggungjawabkan, dan jawabannya bergantung pada seberapa ketat tenggat waktumu.

  • Web API dan microservice biasa. Bukan masalah. Jeda setengah milidetik di request yang latensi jaringannya 30–50 ms nggak akan terasa siapa pun, dan sebagai gantinya kamu dapat bahasa yang bebas dari satu kelas bug paling mahal. Microsoft sendiri bilang sekitar 70% CVE yang mereka perbaiki tiap tahun itu masalah memory safety, dan itu di kode C dan C++.
  • Game yang ngejar 60 FPS atau lebih. Coba bayangin deh, di 60 FPS satu frame cuma punya 16,6 ms, dan di 144 FPS sisanya tinggal 6,9 ms. Jeda GC 10 ms aja udah cukup bikin frame-nya patah-patah.
  • Kernel, driver, dan sistem hard real-time. Di sini yang penting bukan kecepatan rata-rata, tapi determinisme. Sistem yang cuma boleh telat mikrodetik nggak bisa nyerahin jadwalnya ke GC.
  • High-frequency trading. Yang diperdagangkan di sini mikrodetik. Distribusi jeda yang nggak bisa ditebak sama mahalnya dengan jedanya sendiri.

Lihat polanya: yang bikin GC jadi masalah bukan "GC lambat", tapi "GC nggak bisa dijadwalkan". Selama tenggat aplikasimu longgar, tuduhan itu jatuh dengan sendirinya.

Jadi, GC itu masalah atau bukan?

Buat sebagian besar software yang kamu bangun, jawabannya: bukan. GC menukar sedikit RAM dan sedikit CPU dengan hilangnya satu kelas bug paling mahal yang pernah ada di dunia C—dan itu Microsoft sendiri yang bilang, bukan penggemar Go.

Instagram malah bisa mematikan cyclic collector di Python-nya dan langsung jalan 10% lebih efisien tanpa pindah bahasa. Tapi itu cuma masuk akal karena prosesnya berumur pendek dan sering di-restart.

Jadi yang perlu kita lakukan bukan menghindari GC, tapi tahu kapan dia mulai kebebanan. Kalau grafik latency-mu punya spike rapi tiap beberapa menit, tersangkanya mungkin GC, dan kamu bahkan bisa nebak dari durasi spike-nya. Tapi buktikan dulu pakai profiler, jangan cuma dari perasaan.

Selamat membedah grafik latency-mu, dan semoga tersangkanya bukan yang salah tangkap. 👋

Biar makin jago ngoding.

Yuk gabung bareng temen-temen developer lainnya buat dapet update teknologi, tips, dan tutorial santai tiap minggu.

Biar Nggak Kudet

Update teknologi, tips ngoding, dan insight santai langsung ke email kamu tiap minggu.

Santai, anti spam. Bisa berhenti langganan kapan aja.

Baca Selanjutnya

Lihat semua

© 2026 Inva.dev.