Bellek sızıntısı — mobil geliştirmedeki en sinsi sorunlardan biridir. Uygulamanın bellek kullanımı, işletim sistemi tarafından belirlenen sınıra ulaşana kadar istikrarlı bir şekilde büyür ve ardından OutOfMemoryError veya zorla sonlandırma gelir. Square Engineering'e göre, Android uygulamalarının yaklaşık %40'ı yalnızca profilleme yoluyla tespit edilebilen en az bir bellek sızıntısına sahiptir. Bellek büyümesinin nedenlerini ve önleme yöntemlerini inceleyelim.
Önemli Noktalar
Bellek sızıntısı — uygulama tarafından artık ihtiyaç duyulmayan bir nesnenin, GC Kök kümesinden aktif bir referans hala onu işaret ettiği için yığında tutulmaya devam etmesi durumudur. Çöp toplayıcı böyle bir nesneyi canlı olarak kabul eder ve kaldırmaz.
Bellek şişmesi — uygulamanın mevcut görevlerini gerçekleştirmek için gerekenden daha fazla bellek tüketmesiyle ilgili daha geniş bir sorundur. Nedenleri: aşırı önbellekleme, nesne kopyalama, optimal olmayan veri yapıları ve yığın parçalanması.
Android'de her uygulamaya sınırlı bir yığın ayrılır (genellikle cihaza ve işletim sistemi sürümüne bağlı olarak 64–512 MB). iOS'ta sınır daha az katıdır, ancak sınıra yaklaşıldığında sistem bir bellek uyarısı gönderir.
| Özellik | Android | iOS |
|---|---|---|
| Yığın sınırı | 64–512 MB (cihaza bağlı) | Örtük (sistem) |
| Çöp toplama | ART (Eşzamanlı, Kompakt) | ARC (Otomatik Referans Sayma) |
| Sızıntı mekanizması | GC Kök referansları | Tutma döngüleri (güçlü referans döngüleri) |
| Sonuç | OutOfMemoryError | Bellek uyarısı → sonlandırma |
Facebook Engineering Blog'a göre, bellek sızıntıları mobil uygulamalardaki crash raporlarının ~%15'ine neden olur. Android'de, bellek azaldığında sık GC duraklamaları nedeniyle ANR'ler eklenir.
Activity'ye statik referans — klasik bir Android sızıntısı. Statik bir alan veya singleton bir Activity'ye referans tutarsa, singleton canlı olduğu sürece finish()'ten sonra bile GC tarafından toplanmaz. Activity, görünüm hiyerarşisi, kaynaklar ve Context içeren ağır bir nesnedir.
object LeakHolder {
var activityRef: Activity ?= null // leak: static reference to Activity
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle ?= null) {
super.onCreate(savedInstanceState)
LeakHolder.activityRef = this // ❌ MainActivity will never be GC'd
}
}
Anonim sınıflar ve lambdalar — dış sınıfa örtük olarak bir referans tutar. Bir Runnable veya Callback harici bir servise aktarılırsa ve Activity yok edilirse, anonim sınıf nesnesi hala kuyruktadır ve Activity'nin çöp toplamasını engeller.
iOS'ta temel sorun tutma döngüleridir: iki nesne birbirine güçlü referanslar tutar ve ARC, hiçbiri için referans sayacını sıfırlayamaz. Tipik bir durum: self'i güçlü bir şekilde yakalayan bir closure ve closure'a referans tutan self.
LeakCanary — Android'de otomatik sızıntı tespiti için Square'den bir kütüphane. Bir Activity veya Fragment yok edildikten sonra, nesnenin GC tarafından toplanıp toplanmadığını kontrol eder. Toplanmadıysa, bir yığın dökümü alır ve sızıntı izini gösterir.
// LeakCanary 2.x — auto-integration via Application
class ExampleApplication : Application() {
override fun onCreate() {
super.onCreate()
// LeakCanary auto-installs in debug build
// via ContentProvider — zero code setup
}
}
// Force check invocation
AppWatcher.objectWatcher.watch(watchedObject, "leak description")
Android Studio Profiler — gerçek zamanlı bellek izleme için yerleşik bir araçtır. Yığın dökümü kaydetmeye, şüpheli nesneleri (Retained Size > 1 MB) bulmaya ve her nesneye GC kök yolunu izlemeye olanak tanır.
iOS için Xcode Memory Graph Debugger'ı kullanın. Bellekteki nesne grafiğini görselleştirir, tutma döngülerini gösterir ve dairesel referansları anında tespit etmenizi sağlar. Uzun vadeli izleme için Instruments > Allocations da mevcuttur.
WeakReference — çöp toplamayı engellememesi gereken referanslar için temel bir mekanizmadır. GC bir nesneyi toplamaya karar verirse, WeakReference null döndürür. Geri aramalar, dinleyiciler ve arka plan iş parçacıklarından UI bileşenlerine yapılan referanslar için kullanılır.
Lifecycle-aware bileşenler — Android Jetpack'te (Lifecycle, LiveData, Flow, coroutines) uygulanan mimari bir yaklaşımdır. Abonelikler onDestroy'de otomatik olarak iptal edilir ve ana sızıntı sınıfını ortadan kaldırır.
class MyViewModel : ViewModel() {
private val _data = MutableLiveData<List<User>>()
val data: LiveData<List<User>> get() = _data
fun loadData() {
viewModelScope.launch {
val result = repository.fetchData()
_data.postValue(result)
// coroutine auto-cancels on onCleared()
}
}
}
viewModelScope ve lifecycleScope — Android'de yerleşik CoroutineScope'lardır ve ilgili yaşam döngüsü olayında iptal edilir. Bu, modern Android geliştirmede en yaygın senaryo olan coroutine'ler aracılığıyla sızıntıları ortadan kaldırır.
Android Studio'daki Memory Profiler — yığın izleme için birincil araçtır. Canlı ayırmaları, yığın anlık görüntülerini ve türe göre nesne sayımlarını gösterir. Bir döküm kaydetmeye ve şüpheli nesneleri bulmak için MAT (Memory Analyzer Tool) ile analiz etmeye olanak tanır.
Eclipse MAT — bir masaüstü yığın dökümü analizörüdür. Android Studio'dan bir HPROF dosyası yükledikten sonra MAT, bir dominator ağacı oluşturur, her nesnenin tutma boyutunu gösterir ve Leak Suspects Report aracılığıyla otomatik şüpheli sızıntı analizi sunar.
Xcode Memory Graph — görsel bir tutma döngüsü hata ayıklayıcısıdır. Memory Graph Debugger düğmesine tıklandığında, Xcode uygulamayı durdurur, bellekte tam bir nesne grafiği oluşturur ve tutma döngülerini kırmızıyla vurgular.
| Araç | Platform | Özellik |
|---|---|---|
| LeakCanary | Android | Yok etme sonrası otomatik sızıntı tespiti |
| Memory Profiler | Android Studio | Yığın dökümü + canlı ayırmalar |
| Eclipse MAT | Android | Dominator ağacı, Leak Suspects Report |
| Memory Graph | iOS (Xcode) | Tutma döngüsü görselleştirici |
Google I/O 2023'e göre, hata ayıklama yapılarında LeakCanary kullanan uygulamalar, benimsemeden sonraki ilk 2 ayda bellek ile ilgili crash'leri %30–50 oranında azaltır. Proje onboarding aşamasında LeakCanary eklenmesi önerilir.
Sıkça Sorulan Sorular
Sızıntı — koda erişilemeyen ancak aktif referanslar nedeniyle GC tarafından toplanmayan nesneler. Şişme — uygulamanın mantıksal olarak gerekli ancak aşırı miktarda nesne tutması (örneğin, 80 MB çalışan bir uygulamada 50 MB önbellek). Şişme mimari olarak, sızıntı ise doğru referans yönetimiyle çözülür.
LeakCanary ObjectWatcher kullanır — bir Activity'nin onDestroy()'inden sonra Activity üzerinde bir WeakReference oluşturur ve GC'yi çalıştırır. 5 saniye sonra WeakReference temizlenmezse, LeakCanary bir yığın dökümü alır, GC Root'tan nesneye en kısa referans zincirini analiz eder ve dosya ve kod satırıyla birlikte tam sızıntı yığınını gösterir.
Bitmap, Java yığınının dışında yerel bellekte (native heap) yer kaplar. Bir Bitmap'in boyutu = genişlik × yükseklik × 4 bayt (ARGB_8888). 12 MP'lik bir fotoğraf (4000×3000) 48 MB yer kaplar. Android her zaman yerel belleği zamanında boşaltamaz, bu nedenle birden fazla Bitmap birikmesi, yeterli Java yığını olsa bile OOM'ye yol açar.
Tutma döngüsü — ARC'de iki nesnenin birbirine güçlü referanslar tuttuğu ve referans sayacının asla sıfıra ulaşmadığı bir durumdur. Tipik bir örnek: bir closure'a güçlü referansı olan bir ViewController ve self'i güçlü bir şekilde yakalayan closure. Çözüm: closure'larda [weak self] veya [unowned self] kullanın.
Yığın boyutu cihaza ve Android sürümüne bağlıdır. Eski cihazlar (API 15–24) için — 64–128 MB. Modern cihazlar (API 25+) için — 256–512 MB. Kesin değer ActivityManager.getMemoryClass() ile alınabilir. Büyük uygulamalar (oyunlar, düzenleyiciler) için manifest'te largeHeap=true, 1 GB'a kadar sağlar.
Ö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