Yaddaş sızması (memory leak) — tətbiqin artıq lazım olmayan obyektlər tərəfindən tutulmuş yaddaşı boşaltmaması vəziyyətidir. Mobil inkişafda bu xüsusilə kritikdir: məhdud heap və swap-ın olmaması OutOfMemoryError və tətbiqin çökməsinə səbəb olur. Purdue University məlumatına görə (2022), Google Play-dəki Android tətbiqlərinin 35%-i ən azı bir yaddaş sızması ehtiva edir. Tipik ssenariləri, diaqnostika alətlərini və aradan qaldırma üsullarını nəzərdən keçirək.
Əsas məqamlar
Yaddaş sızması — ayrılmış yaddaşın obyekt proqram üçün artıq lazım olmadıqdan sonra sistemə qaytarılmaması vəziyyətidir. Zibil toplayıcı belə bir obyekti canlı hesab edir, çünki ona GC Root-dan aktiv referans zənciri aparır.
Java/Kotlin-də zibil toplayıcı avtomatik işləyir, lakin obyektə texniki referans varsa, onun məntiqi olaraq lazım olmadığını müəyyən edə bilməz. Tərtibatçı lazımsız əlaqələri açıq şəkildə qırmalıdır. Swift/Objective-C-də ARC avtomatik olaraq referansları sayır, lakin retain cycles sayğacın sıfırlanmasını bloklayır.
Sızmaların əsas təhlükəsi — kumulyativ effektdir. Hər sızma az miqdarda yaddaş yeyir, lakin ekranlar arasında çoxsaylı keçidlərdə (ekranın döndərilməsi, Activity-nin açılması/bağlanması) sızmalar yığılır və heap limiti tükənənə qədər davam edir.
Sızma — obyekt kod üçün əlçatmazdır, lakin GC tərəfindən silinməyib. Şişmə — obyekt məntiqi olaraq lazımdır, lakin həddindən artıq miqdarda saxlanılır. Şişmə nümunəsi: 30 MB iş dəsti ilə 100 MB-lıq şəkil keşi. Hər iki problem OOM-a gətirib çıxarır, lakin səbəblər və müalicə üsulları fərqlidir.
ART (Android Runtime) concurrent compaction ilə nəsil əsaslı zibil yığımından istifadə edir. Yaddaş gənc nəsil (Young), yaşlı (Old) və nəhəng obyektlərə (Large) bölünür. Bir neçə GC dövrü keçirmiş obyektlər Old generation-a köçürülür, burada yığım daha az baş verir — bu, adi dövrləri sürətləndirir.
GC heap müəyyən bir doluluq həddinə çatdıqda başlayır (adətən 75-85%). GC zamanı tətbiqin bütün thread-ləri dayandırılır (STW — Stop The World). Nə qədər çox canlı obyekt varsa, fasilə bir o qədər uzun olur. Sızmalar canlı obyektlərin sayını artıraraq GC fasilələrini uzadır.
Zibil toplayıcı canlı obyektləri GC Roots-dan başlayaraq qrafı gəzərək təyin edir: statik sahələr, aktiv thread-ların yığın dəyişənləri, JNI referansları. Bu köklərdən referanslar vasitəsilə əldə edilə bilən hər hansı bir obyekt canlı hesab olunur — hətta tərtibatçı onun artıq lazım olmadığını bilsə belə.
// Nümunə: statik kolleksiya GC Root kimi — daimi sızma
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-ni bloklamır — düzgün davranış
}
}
WeakReference problemi həll edir: GC canlı obyektləri təyin edərkən weak referansları nəzərə almır. Əgər obyektə yalnız weak referanslar qalıbsa, o, növbəti GC dövründə yığılacaq.
Activity Context — Android-də ən kütləvi sızma ssenarisidir. Əgər singleton, statik sahə və ya uzunömürlü xidmət Activity Context-ə referans saxlayırsa, bütün View-lərlə birlikdə Activity GC tərəfindən yığıla bilməz. Həll yolu: uzunömürlü obyektlər üçün Application Context istifadə edin.
Handler və göndərilmiş mesajlar — Handler.postDelayed(runnable, delay) Main Looper növbəsinə mesaj qoyur. Əgər Activity gecikmə müddəti bitməmiş məhv edilərsə, mesaj hələ də növbədədir və Runnable → anonim sinif → xarici sinif (Activity) vasitəsilə referansı saxlayır.
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) // məcburi: növbəni təmizlə
super.onPause()
}
}
Inner Classes — qeyri-statik daxili sinif xarici sinifin nümunəsinə gizli referansa malikdir. Əgər xarici sinif Activity-dirsə və daxili sinif kənara ötürülübsə (məsələn, RecyclerView.Adapter-ə), Activity yığıla bilməz.
Android Studio Memory Profiler — real vaxtda heap-i izləmək üçün quraşdırılmış alət. İşğal olunmuş yaddaş qrafikini, ayrılmaların sayını və tiplər üzrə obyektləri göstərir. Heap dump yazmağa və MAT-da təhlil üçün HPROF formatına ixrac etməyə imkan verir.
Eclipse MAT (Memory Analyzer Tool) — heap dump-ın masaüstü analizatoru. Avtomatik olaraq Leak Suspects Report yaradır, ən böyük retained size olan obyektləri vurğulayır və hər şübhəli obyekt üçün ehtimal olunan GC root chain təklif edir.
Xcode Memory Graph Debugger — iOS üçün. Tətbiqi dayandırır və obyekt qrafını vizuallaşdırır. Retain cycles qırmızı rənglə vurğulanır, istənilən obyektə klikləyib onun retain count və referanslarını görmək olar.
| Alət | İmkanlar | Mürəkkəblik |
|---|---|---|
| Memory Profiler | Real-vaxt qrafiki, heap dump, Object Allocation Tracking | Aşağı |
| Eclipse MAT | Dominator tree, Leak Suspects, OQL sorğuları | Orta |
| LeakCanary | Avtomatik aşkarlama, bildirişdə sızma izi | Minimal |
| Xcode Memory Graph | Retain cycles-ın vizual qrafı, canlı obyekt siyahısı | Aşağı |
Uber Engineering Blog məlumatına görə, CI/CD pipeline-da avtomatik yaddaş profilləməsinin (LeakCanary + heap dump təhlili) tətbiqi 3 ay ərzində istehsalatda yaddaşla bağlı insidentlərin sayını 60% azaldır.
Context-in dəyişdirilməsi — obyekt Activity-dən uzun yaşayırsa, applicationContext istifadə edin. Bütün uzunömürlü obyektlər (singletonlar, repozitoriyalar, database helpers) Application Context almalıdır, Activity Context yox. İstisna: Activity üçün spesifik olan tema və ya resurslara girişə ehtiyacı olan UI komponentləri.
Lifecycle-aware komponentlər — LifecycleObserver, DefaultLifecycleObserver və ya reactivex istifadəsi onDestroy zamanı abunəlikləri avtomatik ləğv edir. Android Jetpack lifecycleScope və viewModelScope təqdim edir, onlar müvafiq hadisə ilə təmizlənir.
Static inner class — daxili sinif xarici sinfin sahələrinə girişə ehtiyac duymursa, onu static edin. Statik daxili sinif xarici sinfə gizli referansa malik deyil. Giriş lazımdırsa, açıq referans üçün WeakReference istifadə edin.
class MyActivity : AppCompatActivity() {
// ❌ Qeyri-statik daxili sinif — MyActivity-ə gizli referans
inner class BadListener : SomeListener {
override fun onEvent() { /*...*/ }
}
// ✅ Statik daxili sinif — gizli referans yoxdur
class GoodListener(private val activityRef: WeakReference<MyActivity>) : SomeListener {
override fun onEvent() { /*...*/ }
}
}
iOS-da capture list-lərdən istifadə edin: yaradıcıdan uzun yaşaya biləcək bağlanmalarda [weak self]. Delegate-lər üçün zəif referanslardan istifadə edin (weak var delegate). Yalnız self-in həyatı zamanı çağrılacağına zəmanət olan bağlanmalar üçün [unowned self] istifadə edilə bilər, lakin ehtiyatla — boşaldılmış obyektə müraciət crash-ə səbəb olacaq.
Tez-tez verilən suallar
Android-də ekranlar arasında bir neçə keçid edin (Activity A → B → A → B) və adb shell dumpsys meminfo package_name yoxlayın. Total PSS sabit şəkildə artırsa və ilkin dəyərə qayıtmırsa — sızma var. iOS-da eyni şəkildə: vizual yoxlama üçün Xcode-da Debug Memory Graph istifadə edin.
Bəli, əgər CoroutineScope komponent məhv edilərkən ləğv edilməzsə. GlobalScope-da işə salınmış korutina Activity-nin finish() çağırışından sonra da işləməyə davam edir. Həll yolu: viewModelScope (onCleared-də ləğv edilir) və ya lifecycleScope (onDestroy-də ləğv edilir) istifadə edin. Öz Scope-larınız üçün LifecycleOwner vasitəsilə lifecycle-aware scopes yaradın.
Bitmap piksel məlumatlarını Java heap-də deyil, native heap-də saxlayır. Bu o deməkdir ki, Java GC Bitmap-in real ölçüsünü görmür. Bitmap-də recycle() çağırılmazsa və ya referans sıfırlanmazsa, native yaddaş boşalmayacaq. Kiçildilmiş surətləri yükləmək üçün BitmapFactory-dən inSampleSize ilə və keşin avtomatik idarə edilməsi üçün Glide/Coil istifadə edin.
Statik sahə — GC Root-dur. Sinif yükləndiyi müddətcə yaşayır (Android-də — Process yaşadığı müddətcə). Statik sahə Activity, Bitmap, View və ya hər hansı digər ağır obyektə referans edirsə, bu obyekt heç vaxt GC tərəfindən yığılmayacaq. Statik sahə — əbədi referansdır. Həll yolu: yalnız WeakReference saxlayın və ya onDestroy-də statik sahəni sıfırlayın.
ARC güclü referansların sayı sıfıra düşdükdə obyektləri avtomatik boşaldır. Retain cycle — ARC-də sızmanın yeganə yoludur. Həmişə parent→child referansları üçün weak istifadə edin, burada child parent-dən uzun yaşamalıdır (delegatlar, data source). Bağlanmalar üçün capture list [weak self] istifadə edin və bağlanma daxilində self-i nil-ə yoxlayın.
Nəticə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun