Kebocoran memori: apa itu, skenario tipikal dan diagnosis

Penulis: IT Sectr Diterbitkan: 2026-07-29 Waktu membaca: 10 mnt

Kebocoran memori (memory leak) — situasi ketika aplikasi tidak membebaskan memori yang ditempati oleh objek yang tidak lagi diperlukan. Dalam pengembangan mobile, hal ini sangat kritis: heap terbatas dan tidak adanya swap menyebabkan OutOfMemoryError dan crash aplikasi. Menurut Purdue University (2022), 35% aplikasi Android di Google Play mengandung setidaknya satu kebocoran memori. Mari kita bahas skenario tipikal, alat diagnostik, dan metode perbaikan.

Poin utama

  • GC Root — titik masuk yang digunakan garbage collector untuk menentukan objek hidup
  • Kebocoran Context — mengirimkan Activity Context ke singleton menyebabkan seluruh hierarki View tertahan
  • Handler dengan postDelayed — jika Activity dihancurkan, Handler tidak membiarkannya menuju GC
  • Heap dump — metode utama analisis kebocoran melalui MAT atau Android Profiler
  • SoftReference — alternatif WeakReference untuk cache dengan pembersihan otomatis saat kekurangan memori

Apa itu kebocoran memori dalam aplikasi mobile?

Kebocoran memori — adalah situasi ketika memori yang dialokasikan tidak dikembalikan ke sistem setelah objek tidak lagi diperlukan oleh program. Garbage collector menganggap objek tersebut hidup karena ada rantai referensi aktif dari GC Root ke objek tersebut.

Di Java/Kotlin, garbage collector bekerja secara otomatis, tetapi tidak dapat menentukan bahwa suatu objek secara logis tidak diperlukan jika ada referensi teknis ke objek tersebut. Pengembang harus secara eksplisit memutus koneksi yang tidak perlu. Di Swift/Objective-C, ARC secara otomatis menghitung referensi, tetapi retain cycles memblokir penghitungan ulang ke nol.

Bahaya utama kebocoran — efek kumulatif. Setiap kebocoran mengonsumsi sejumlah kecil memori, tetapi dengan banyak perpindahan antar layar (rotasi layar, membuka/menutup Activity), kebocoran menumpuk hingga batas heap habis.

Apa perbedaan kebocoran dengan pembengkakan?

Kebocoran — objek tidak dapat diakses oleh kode, tetapi tidak dihapus oleh GC. Pembengkakan — objek secara logis diperlukan, tetapi disimpan dalam jumlah berlebihan. Contoh pembengkakan: cache gambar 100 MB dengan set kerja 30 MB. Kedua masalah menyebabkan OOM, tetapi penyebab dan metode penanganannya berbeda.

Bagaimana cara kerja garbage collector dan mengapa kebocoran terjadi?

ART (Android Runtime) menggunakan garbage collection generasional dengan concurrent compaction. Memori dibagi menjadi generasi muda (Young), tua (Old), dan objek besar (Large). Objek yang telah bertahan beberapa siklus GC dipindahkan ke Old generation, di mana pengumpulan lebih jarang terjadi — ini mempercepat siklus biasa.

GC dimulai ketika heap mencapai ambang okupansi tertentu (biasanya 75-85%). Selama GC, semua thread aplikasi dijeda (STW — Stop The World). Semakin banyak objek hidup, semakin lama jeda. Kebocoran meningkatkan jumlah objek hidup, memperpanjang jeda GC.

Pengumpul menentukan objek hidup dengan melintasi graf dari GC Roots: field statis, variabel stack dari thread aktif, referensi JNI. Objek apa pun yang dapat dijangkau melalui referensi dari akar-akar ini dianggap hidup — bahkan jika pengembang tahu bahwa objek itu tidak lagi diperlukan.

kotlin
// Contoh: koleksi statis sebagai GC Root — kebocoran permanen
object GlobalHolder {
    val listeners = mutableListOf<WeakReference<Any>>()
}

class LeakingFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle ?= null) {
        super.onCreate(savedInstanceState)
        GlobalHolder.listeners.add(WeakReference(this))
        // WeakReference tidak memblokir GC — perilaku yang benar
    }
}

WeakReference memecahkan masalah: GC mengabaikan weak reference saat menentukan objek hidup. Jika hanya weak reference yang tersisa ke suatu objek, objek tersebut akan dikumpulkan dalam siklus GC berikutnya.

Skenario tipikal kebocoran di Android dan iOS

Activity Context — skenario kebocoran paling masif di Android. Jika singleton, field statis, atau layanan berumur panjang menyimpan referensi ke Activity Context, seluruh Activity dengan semua View tidak dapat dikumpulkan oleh GC. Solusi: gunakan Application Context untuk objek berumur panjang.

Handler dan pesan yang dikirim — Handler.postDelayed(runnable, delay) menempatkan pesan dalam antrian Main Looper. Jika Activity dihancurkan sebelum penundaan berakhir, pesan masih dalam antrian dan menyimpan referensi melalui Runnable → kelas anonim → kelas eksternal (Activity).

kotlin
class SafeActivity : AppCompatActivity() {
    private val mainHandler = Handler(Looper.getMainLooper())
    private val callback = Runnable { /* update UI */ }

    override fun onResume() {
        super.onResume()
        mainHandler.postDelayed(callback, 5000)
    }

    override fun onPause() {
        mainHandler.removeCallbacks(callback) // wajib: bersihkan antrian
        super.onPause()
    }
}

Inner Classes — kelas dalam non-statis memiliki referensi implisit ke instance kelas luar. Jika kelas luar adalah Activity dan kelas dalam diteruskan ke suatu tempat di luar (misalnya, ke RecyclerView.Adapter), Activity tidak dapat dikumpulkan.

  • TimerTask dan ScheduledExecutorService — tugas yang dijadwalkan sebelum penghancuran Activity
  • BroadcastReceiver — tidak didaftarkan di onPause/onDestroy terus memegang Context
  • ViewModel dengan referensi ke View — ViewModel hidup lebih lama dari Activity, referensi ke View menyebabkan kebocoran
  • Retrofit Call — jika Call tidak dibatalkan, respons datang ke Fragment yang telah dihancurkan

Alat diagnostik kebocoran memori

Android Studio Memory Profiler — alat bawaan untuk memantau heap secara real-time. Menampilkan grafik memori yang terpakai, jumlah alokasi dan objek berdasarkan jenis. Memungkinkan merekam heap dump dan mengekspor ke format HPROF untuk analisis di MAT.

Eclipse MAT (Memory Analyzer Tool) — analisator heap dump desktop. Secara otomatis membuat Leak Suspects Report yang menyoroti objek dengan retained size terbesar dan menyarankan rantai GC root yang mungkin untuk setiap objek yang mencurigakan.

Xcode Memory Graph Debugger — untuk iOS. Menjeda aplikasi dan memvisualisasikan graf objek. Retain cycles disorot dengan warna merah, Anda dapat mengklik objek apa pun untuk melihat retain count dan referensinya.

AlatKemampuanKompleksitas
Memory ProfilerGrafik real-time, heap dump, pelacakan alokasi objekRendah
Eclipse MATPohon dominator, tersangka kebocoran, kueri OQLSedang
LeakCanaryDeteksi otomatis, jejak kebocoran dalam notifikasiMinimal
Xcode Memory GraphGraf visual retain cycles, daftar objek hidupRendah

Menurut Uber Engineering Blog, penerapan profiling memori otomatis (LeakCanary + analisis heap dump) dalam pipeline CI/CD mengurangi jumlah insiden terkait memori di produksi sebesar 60% dalam 3 bulan.

Metode perbaikan kebocoran

Mengganti Context — jika objek hidup lebih lama dari Activity, gunakan applicationContext. Semua objek berumur panjang (singleton, repositori, database helpers) harus menerima Application Context, bukan Activity Context. Pengecualian: komponen UI yang membutuhkan akses ke tema atau resource spesifik Activity.

Komponen Lifecycle-aware — penggunaan LifecycleObserver, DefaultLifecycleObserver, atau reactivex secara otomatis membatalkan langganan saat onDestroy. Android Jetpack menyediakan lifecycleScope dan viewModelScope, yang dibersihkan oleh event yang sesuai.

Static inner class — jika kelas dalam tidak memerlukan akses ke field kelas luar, buatlah static. Kelas dalam statis tidak memiliki referensi implisit ke kelas luar. Jika akses diperlukan, gunakan WeakReference untuk referensi eksplisit.

kotlin
class MyActivity : AppCompatActivity() {

    // ❌ Kelas dalam non-statis — referensi implisit ke MyActivity
    inner class BadListener : SomeListener {
        override fun onEvent() { /*...*/ }
    }

    // ✅ Kelas dalam statis — tanpa referensi implisit
    class GoodListener(private val activityRef: WeakReference<MyActivity>) : SomeListener {
        override fun onEvent() { /*...*/ }
    }
}

Di iOS, gunakan capture lists: [weak self] dalam closure yang dapat hidup lebih lama dari pembuatnya. Untuk delegasi, gunakan referensi lemah (weak var delegate). Untuk closure yang dijamin hanya dipanggil selama hidup self, dapat menggunakan [unowned self], tetapi hati-hati — mengakses objek yang telah dibebaskan akan menyebabkan crash.

Pertanyaan yang sering diajukan

Bagaimana menemukan kebocoran tanpa alat khusus?

Di Android, lakukan beberapa kali perpindahan antar layar (Activity A → B → A → B) dan periksa adb shell dumpsys meminfo package_name. Jika Total PSS terus meningkat dan tidak kembali ke nilai awal — ada kebocoran. Di iOS serupa: gunakan Debug Memory Graph di Xcode untuk pemeriksaan visual.

Bisakah coroutine Kotlin menyebabkan kebocoran?

Ya, jika CoroutineScope tidak dibatalkan saat komponen dihancurkan. Coroutine yang dijalankan di GlobalScope terus berjalan bahkan setelah finish() Activity. Solusi: gunakan viewModelScope (dibatalkan di onCleared) atau lifecycleScope (dibatalkan di onDestroy). Untuk Scope kustom, buat lifecycle-aware scopes melalui LifecycleOwner.

Bagaimana Bitmap mempengaruhi kebocoran?

Bitmap menyimpan data piksel di native heap, bukan di Java heap. Ini berarti Java GC tidak melihat ukuran sebenarnya Bitmap. Jika Bitmap tidak dipanggil recycle() atau referensi tidak di-null-kan, memori native tidak akan dibebaskan. Gunakan BitmapFactory dengan inSampleSize untuk memuat saluran yang diperkecil dan Glide/Coil untuk manajemen cache otomatis.

Apa itu kebocoran melalui field statis?

Field statis — adalah GC Root. Ia hidup selama kelas dimuat (di Android — selama Process hidup). Jika field statis mereferensikan Activity, Bitmap, View, atau objek berat lainnya, objek tersebut tidak akan pernah dikumpulkan oleh GC. Field statis — referensi abadi. Solusi: simpan hanya WeakReference atau null-kan field statis di onDestroy.

Bagaimana menghindari kebocoran di iOS dengan ARC?

ARC secara otomatis membebaskan objek ketika penghitung referensi kuat turun ke nol. Retain cycle — satu-satunya cara kebocoran di ARC. Selalu gunakan weak untuk referensi parent→child, di mana child harus hidup lebih lama dari parent (delegasi, data source). Untuk closure, gunakan capture list [weak self] dan periksa self untuk nil di dalam closure.

Kesimpulan

  • Kebocoran memori — objek tidak dapat diakses oleh kode, tetapi tidak dihapus oleh GC karena ada referensi aktif dari GC Root
  • GC Roots mencakup field statis, variabel stack, dan referensi JNI; objek apa pun yang dapat dijangkau darinya adalah hidup
  • Kebocoran Context — masalah paling masif di Android: mengirimkan Activity Context ke singleton atau field statis
  • Handler dan Inner Class — penyebab kedua paling sering: pesan yang tidak dibatalkan dalam antrian Looper menyimpan referensi ke Activity
  • LeakCanary — alat standar deteksi otomatis; membuat heap dump dan menunjukkan rantai GC root yang tepat
  • lifecycleScope dan viewModelScope memecahkan masalah kebocoran melalui coroutine — pembatalan otomatis saat destroy
  • Profil memori di CI/CD: LeakCanary di debug + analisis heap dump dalam uji coba harus memblokir merge jika ada kebocoran baru

Kami akan mengembangkan aplikasi seluler turnkey

IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.

Diskusikan proyek

Baca juga