Bellek sızıntısı (memory leak) — bir uygulamanın artık ihtiyaç duyulmayan nesnelerin kapladığı belleği serbest bırakmaması durumudur. Mobil geliştirmede bu özellikle kritiktir: sınırlı heap ve swap eksikliği OutOfMemoryError ve uygulama çökmesine yol açar. Purdue University'ye (2022) göre, Google Play'deki Android uygulamalarının %35'i en az bir bellek sızıntısı içerir. Tipik senaryoları, teşhis araçlarını ve düzeltme yöntemlerini inceleyelim.
Önemli Noktalar
Bellek sızıntısı — ayrılan belleğin, nesne program tarafından artık ihtiyaç duyulmadıktan sonra sisteme geri verilmemesi durumudur. Çöp toplayıcı, GC Root'tan ona işaret eden aktif bir referans zinciri olduğu için böyle bir nesneyi canlı olarak kabul eder.
Java/Kotlin'de çöp toplayıcı otomatik olarak çalışır, ancak teknik bir referans varsa bir nesnenin mantıksal olarak gereksiz olduğunu belirleyemez. Geliştirici gereksiz bağlantıları açıkça koparmalıdır. Swift/Objective-C'de ARC otomatik olarak referansları sayar, ancak retain cycle'lar sayacın sıfıra düşmesini engeller.
Sızıntıların ana tehlikesi kümülatif etkidir. Her sızıntı az miktarda bellek tüketir, ancak tekrarlanan ekran geçişleriyle (ekran döndürme, Activity açma/kapama) sızıntılar heap sınırı tükenene kadar birikir.
Sızıntı — nesne koddan erişilemez ancak GC tarafından kaldırılmamıştır. Şişkinlik — nesne mantıksal olarak gereklidir ancak aşırı miktarda depolanır. Şişkinlik örneği: 30 MB çalışma setine sahip 100 MB görüntü önbelleği. Her iki sorun da OOM'ye yol açar, ancak nedenler ve tedavi yöntemleri farklıdır.
ART (Android Runtime) eşzamanlı sıkıştırma ile nesil bazlı çöp toplama kullanır. Bellek Genç (Young), Yaşlı (Old) ve Büyük nesneler (Large) olarak ayrılır. Birkaç GC döngüsünden kurtulan nesneler, toplamanın daha az sıklıkta olduğu Old nesline taşınır — bu normal döngüleri hızlandırır.
GC, heap belirli bir doluluk eşiğine (genellikle %75-85) ulaştığında başlar. GC sırasında, uygulamanın tüm iş parçacıkları duraklatılır (STW — Stop The World). Ne kadar çok canlı nesne varsa, duraklama o kadar uzun olur. Sızıntılar canlı nesne sayısını artırarak GC duraklamalarını uzatır.
Toplayıcı, GC Roots'tan grafiği dolaşarak canlı nesneleri belirler: statik alanlar, aktif iş parçacıklarının yığın değişkenleri, JNI referansları. Bu köklerden referanslarla ulaşılabilen herhangi bir nesne canlı olarak kabul edilir — geliştirici artık ihtiyaç olmadığını bilse bile.
// Örnek: GC Root olarak statik koleksiyon — kalıcı sızıntı
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 GC'yi engellemez — doğru davranış
}
}
WeakReference sorunu çözer: GC canlı nesneleri belirlerken zayıf referansları yok sayar. Bir nesneye yalnızca zayıf referanslar kaldıysa, en yakın GC döngüsünde toplanacaktır.
Activity Context — Android'de en yaygın sızıntı senaryosu. Bir singleton, statik alan veya uzun ömürlü hizmet bir Activity Context referansı saklarsa, tüm Views ile birlikte Activity'nin tamamı GC tarafından toplanamaz. Çözüm: uzun ömürlü nesneler için Application Context kullanın.
Handler ve gönderilen mesajlar — Handler.postDelayed(runnable, delay) Main Looper kuyruğuna bir mesaj koyar. Gecikme süresi dolmadan Activity yok edilirse, mesaj hala kuyruktadır ve Runnable → anonim sınıf → dış sınıf (Activity) aracılığıyla bir referans tutar.
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) // zorunlu: kuyruğu temizle
super.onPause()
}
}
İç sınıflar — statik olmayan bir iç sınıf, dış sınıf örneğine örtük bir referansa sahiptir. Dış sınıf bir Activity ise ve iç sınıf dışarıya bir yere (örneğin, RecyclerView.Adapter'a) aktarılırsa, Activity toplanamaz.
Android Studio Memory Profiler — gerçek zamanlı heap izleme için yerleşik araç. Kullanılan bellek grafiğini, ayırma sayısını ve türe göre nesneleri gösterir. Heap dump kaydetmeye ve MAT'te analiz için HPROF formatında dışa aktarmaya olanak tanır.
Eclipse MAT (Memory Analyzer Tool) — masaüstü heap dump analizörü. Otomatik olarak Leak Suspects raporları oluşturur, en büyük retained size'a sahip nesneleri vurgular ve her şüpheli nesne için olası GC root chain'ini önerir.
Xcode Memory Graph Debugger — iOS için. Uygulamayı durdurur ve nesne grafiğini görselleştirir. Retain cycle'lar kırmızıyla vurgulanır; herhangi bir nesneye tıklayarak retain count ve referanslarını görebilirsiniz.
| Araç | Yetenekler | Karmaşıklık |
|---|---|---|
| Memory Profiler | Gerçek zamanlı grafik, heap dump, Object Allocation Tracking | Düşük |
| Eclipse MAT | Dominator tree, Leak Suspects, OQL sorguları | Orta |
| LeakCanary | Otomatik algılama, bildirimde sızıntı izi | Minimum |
| Xcode Memory Graph | Görsel retain cycle grafiği, canlı nesne listesi | Düşük |
Uber Engineering Blog'a göre, CI/CD hattına otomatik bellek profillemesi (LeakCanary + heap dump analizi) entegre etmek, 3 ay içinde üretimdeki bellek ile ilgili olayları %60 azaltır.
Context'i değiştirin — bir nesne Activity'den daha uzun yaşıyorsa, applicationContext kullanın. Tüm uzun ömürlü nesneler (singleton'lar, havuzlar, veritabanı yardımcıları) Activity Context değil, Application Context almalıdır. İstisna: Activity'ye özgü tema veya kaynaklara erişmesi gereken UI bileşenleri.
Lifecycle-aware bileşenler — LifecycleObserver, DefaultLifecycleObserver veya reaktif uzantılar kullanmak, onDestroy'da abonelikleri otomatik olarak iptal eder. Android Jetpack, ilgili yaşam döngüsü olayı tarafından temizlenen lifecycleScope ve viewModelScope'u sağlar.
Statik iç sınıf — iç sınıfın dış sınıfın alanlarına erişmesi gerekmiyorsa, onu static yapın. Statik bir iç sınıfın dış sınıfa örtük bir referansı yoktur. Erişim gerekiyorsa, açık referans için WeakReference kullanın.
class MyActivity : AppCompatActivity() {
// ❌ Statik olmayan iç sınıf — MyActivity'ye örtük referans
inner class BadListener : SomeListener {
override fun onEvent() { /*...*/ }
}
// ✅ Statik iç sınıf — örtük referans yok
class GoodListener(private val activityRef: WeakReference<MyActivity>) : SomeListener {
override fun onEvent() { /*...*/ }
}
}
iOS'ta yakalama listeleri kullanın: yaratıcısından daha uzun yaşayabilen closure'larda [weak self]. Temsilciler için zayıf referanslar kullanın (weak var delegate). Yalnızca self'in yaşam süresi boyunca çağrılacağı garanti edilen closure'lar için [unowned self] kullanılabilir, ancak dikkatli olun — serbest bırakılmış bir nesneye erişim çökmeye neden olur.
Sıkça Sorulan Sorular
Android'de birkaç ekran geçişi yapın (Activity A → B → A → B) ve adb shell dumpsys meminfo package_name komutunu kontrol edin. Total PSS istikrarlı bir şekilde büyüyor ve orijinal değere dönmüyorsa — sızıntı var. iOS'ta benzer şekilde: görsel inceleme için Xcode'da Debug Memory Graph kullanın.
Evet, bileşen yok edildiğinde CoroutineScope iptal edilmezse. GlobalScope'da başlatılan bir coroutine, Activity'nin finish()'inden sonra bile çalışmaya devam eder. Çözüm: viewModelScope (onCleared'da iptal edilir) veya lifecycleScope (onDestroy'da iptal edilir) kullanın. Özel kapsamlar için LifecycleOwner aracılığıyla lifecycle-aware kapsamlar oluşturun.
Bitmap piksel verilerini Java heap'inde değil, yerel heap'te (native heap) depolar. Bu, Java GC'nin Bitmap'in gerçek boyutunu görmediği anlamına gelir. Bitmap üzerinde recycle() çağrılmazsa veya referans null yapılmazsa, yerel bellek serbest bırakılmaz. Küçültülmüş kopyaları yüklemek için BitmapFactory'yi inSampleSize ile ve otomatik önbellek yönetimi için Glide/Coil kullanın.
Statik alan bir GC Root'tur. Sınıf yüklü olduğu sürece yaşar (Android'de — Process canlı olduğu sürece). Statik bir alan bir Activity, Bitmap, View veya başka bir ağır nesneye referans veriyorsa, o nesne asla GC tarafından toplanmayacaktır. Statik alan sonsuz bir referanstır. Çözüm: yalnızca WeakReference saklayın veya onDestroy'da statik alanı null yapın.
ARC, güçlü referans sayacı sıfıra düştüğünde nesneleri otomatik olarak serbest bırakır. ARC altında sızıntının tek yolu retain cycle'dır. Çocuğun ebeveynden daha uzun yaşayabileceği ebeveyn→çocuk referansları (temsilciler, veri kaynakları) için her zaman weak kullanın. Closure'lar için yakalama listesi [weak self] kullanın ve closure içinde self'in nil olup olmadığını kontrol edin.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun