Yaddaş sızması (Memory Leak) — proqramın artıq lazım olmayan obyektlərə istinadları saxlayaraq zibil yığanın (garbage collector) occupied memory-i boşaltmasına mane olduğu vəziyyətdir. LeakCanary məlumatlarına görə, hətta yaxşı yazılmış tətbiqlərdə 10 000 kod sətrinə 3–5 sızma rast gəlinir. Hər sızma tədricən mövcud yaddaşı azaldır, yavaşlamalara və OutOfMemoryError-a səbəb olur.
Əsas məqamlar
Yaddaş sızması (Memory Leak) — obyektin güclü istinadlar (Strong Reference) zənciri vasitəsilə əldə edilə bilən qalması, baxmayaraq ki, məntiqi olaraq artıq proqram üçün lazım deyil. Zibil yığan (GC) belə bir obyekti canlı hesab edir və onun tutduğu yaddaşı boşaltmır. Nəticədə, mövcud yığın (Heap) yaddaşı daim azalır və GC fasilələrinin tezliyi artır.
Fərqli olaraq əl ilə yaddaş idarəetməsi olan dillərdən (C, C++), Java/Kotlin-də sızma unudulmuş free() deyil, unudulmuş istinaddır. Kök obyektdən (GC Root) sızan obyektə qədər strong reference mövcud olduğu müddətcə GC onu lazımlı hesab edir. Tipik GC Root: statik sahələr, aktiv iplər, çağırış yığını, JNI-qlobal istinadlar.
Təhlükəsi — sızmaların yığılma effekti. 100 KB-lıq bir sızma nəzərə çarpmır, lakin 100 belə sızma 10 MB yer tutur və tətbiq tez-tez GC səbəbindən yavaşlamağa başlayır. Kritik sızma kütləsi OutOfMemoryError və tətbiqin çökməsinə gətirib çıxarır. Sızma əlamətləri: Profiler qrafikində yaddaş istehlakının daimi artımı, STW (Stop The World) ilə tez-tez GC fasilələri və UI performansının azalması.
Beş növ sızma mobil inkişafda 95% halları əhatə edir. Hər birinin öz səbəbi və kodda xarakterik nümunəsi var.
Ən tanınmış Android sızması — Activity və ya Context-ə statik istinadın saxlanması. Tipik kod: onDestroy() zamanı sıfırlanmayan statik Activity sahəsi. Statik sahə yaşadıqca, 1–10 MB yer tuta bilən View ağacı ilə bütün Activity yaşayır. Bu, LeakCanary-nin ilk növbədə tapdığı klassik sızmadır.
Həll yolu: Activity və ya Context-i heç vaxt statik sahələrdə saxlamayın. Activity-dən daha uzun yaşayan sinqletonlar üçün Application Context istifadə edin. Activity-ə istinad lazımdırsa — WeakReference<Activity> istifadə edin.
object MySingleton {
private var weakActivity: WeakReference<Activity>? = null
fun attach(activity: Activity) {
weakActivity = WeakReference(activity)
}
}
Anonim siniflər və statik olmayan iç-içə siniflər gizli şəkildə ehtiva edən sinfə istinad saxlayır. onDestroy() sonra icra olunan Handler-ə ötürülən Runnable bütün Activity-ni saxlayır. Activity-ni bağlayan Retrofit callback də eyni şeyi edir. Bu ən məkrli sızma növüdür — gizli istinad kodda görünmür.
Kotlin object ifadələri və lambdalar da xarici sinfə istinadları ələ keçirir. İç-içə sinifləri statik (və ya Kotlin-də top-level) edin və xarici istinadları WeakReference vasitəsilə ötürün. Lambdalar üçün viewLifecycleOwner ilə Lifecycle-aware yanaşmasından istifadə edin.
Abunəlik sistem xidmətlərinə abunəlikdən çıxmadan — birbaşa sızma. onResume() qeydiyyatdan keçən, lakin onPause() unregister çağırılmayan SensorManager, LocationManager, NotificationListener Activity-ni saxlayır. Eynilə: CompositeDisposable-a əlavə edilməmiş RxJava Disposable və GlobalScope vasitəsilə işə salınmış korutin.
Lifecycle-aware komponentlərdən istifadə edin: LifecycleOwner ilə observe() onDestroy() zamanı avtomatik abunəlikdən çıxır. RxJava üçün — viewLifecycleOwner.lifecycle.addObserver DisposableObserver ilə. Korutinlər üçün — lifecycleScope.launch() həyat dövrünə bağlıdır.
// Lifecycle vasitəsilə avtomatik abunədən çıxma
viewModel.userData.observe(viewLifecycleOwner) { data ->
updateUI(data)
}
// lifecycleScope ilə korutinlər
lifecycleScope.launch {
viewModel.loadData().collect { render(it) }
}
Bitmap əhəmiyyətli yığın yaddaşı tutur: bir FullHD-bitmap — 1920 × 1080 × 4 bayt = 8.3 MB. Siyahının hər bir elementi üçün Bitmap yaradılırsa və gizlədərkən recycle() çağırılmırsa, yaddaş tez tükənir. Köhnə Android versiyalarında (3.0-dan əvvəl) Bitmap native yaddaşda saxlanılırdı, lakin müasir versiyalarda — Dalvik/ART yığınında və GC onu yalnız strong reference olmadıqda boşalda bilər.
Şəkilləri yükləmək üçün Glide və ya Coil istifadə edin — bu kitabxanalar keşləmə və recycle-i avtomatik idarə edir. Birbaşa Bitmap ilə işləyirsinizsə, artıq göstərilməyən böyük şəkillər üçün bitmap.recycle() çağırın və kiçildilmiş nüsxələri yükləmək üçün inSampleSize istifadə edin.
Fragment iki həyat dövrünə malikdir: Fragment-in özü və onun View-si. onDestroyView() sonra View ağacı məhv edilir, lakin xarici istinad varsa Fragment-in özü yaddaşda qala bilər. Tipik səhv — məhv edildikdə təmizlənməyən ViewPager adapterində və ya naviqasiya qrafında Fragment-ə istinadın saxlanması.
Heç vaxt Fragment-ə istinadı uzunömürlü obyektlərin sahələrində saxlamayın. İç-içə fragmentlər üçün childFragmentManager və aralarında məlumat ötürmək üçün LifecycleOwner ilə observe() istifadə edin. ViewPager2 bu problemi API səviyyəsində həll etdi: FragmentTransactionAdapter həyat dövrünü düzgün idarə edir.
Aşkarlama iki faktın yoxlanılmasını tələb edir: yaddaş expected lifetime sonra qayıtmır və müəyyən tip obyektlərin sayı azalmadan artır. Diaqnostika prosesi üç mərhələdən ibarətdir.
Birinci mərhələ — Android Studio-da Memory Profiler vasitəsilə vizual yoxlama. Memory tabını açın, hədəf hərəkəti edin (ekranı açın və bağlayın), GC (Garbage Collection) düyməsini basın və yaddaşın ilkin səviyyəyə qayıtdığını yoxlayın. 3–4 açma-bağlama dövründən sonra yaddaş davamlı olaraq artırsa — sızma var.
İkinci mərhələ — Heap Dump götürmək. Memory Profiler-də Dump Java Heap düyməsini basın. Alınan .hprof faylını Android Studio-da açın: yığındakı bütün obyektləri ölçüləri və istinadları ilə görəcəksiniz. Ekran bağlandıqdan sonra sayı sıfır olmalı olan sinifləri axtarın. Məsələn, bağlandıqdan sonra sayı 2 olan MainActivity — açıq sızma.
Üçüncü mərhələ — Retained Size və GC Root təhlili. Android Studio-da Retained Size-i təhlil edin: bu obyekti silmək nə qədər yaddaşı boşaldacaq. GC Root-dan obyektə qədər yol onu nəyin saxladığını göstərir: Static field → HashMap → Activity — və siz sızma nöqtəsini görürsünüz. Reference widget paneli obyektin bütün saxlayıcılarını göstərir.
Dörd alət sızmaların aşkarlanmasını avtomatik aşkarlamadan dərin Heap Dump təhlilinə qədər əhatə edir.
| Alət | Metod | Nəticə formatı |
|---|---|---|
| LeakCanary | Avtomatik monitoring | Heap Dump + sızmanın stack trace-i |
| Android Memory Profiler | Əl ilə monitoring | Yaddaş qrafiki + Heap Dump |
| MAT (Eclipse) | Dərin təhlil | Dominator Tree hesabatı + GC Root yolu |
| Perfetto | Sistem səviyyəli trace | Zaman xətti + native yaddaş |
LeakCanary — hər bir Android layihəsi üçün must-have. Activity/Fragment həyat dövrü başa çatdıqdan sonra sızmaları avtomatik aşkarlayır və sızmanın dəqiq yerini stack trace ilə göstərir. İnteqrasiya: build.gradle-da bir sətir. LeakCanary 2.x əl ilə başlatma tələb etmir — avtomatik olaraq Application Watcher qeydiyyatdan keçirir.
Profilaktik tədbirlər hər mərhələdə kodu yoxlayan qaydalar və alətlər dəsti vasitəsilə inkişaf prosesinə daxil edilir.
Heç vaxt Activity, Fragment və ya View-ə istinadı statik sahədə, sinqlletonda və ya uzunömürlü obyektdə saxlamayın. İstinadsız keçinmək mümkün deyilsə — WeakReference istifadə edin və ya məlumatları lazım olduğu qədər yaşayan və birbaşa View saxlamayan ViewModel vasitəsilə saxlayın.
ViewModel və LiveData Android Architecture Components-dən həyat dövrü problemini arxitektura səviyyəsində həll edir. ViewModel ekranın dönməsindən sonra yaşayır və View-ə istinadları ehtiva etmir. LiveData onDestroy() zamanı observer-i avtomatik abunəlikdən çıxarır. Sistem xidmətlərinə əl ilə abunəlik əvəzinə onlardan istifadə edin.
Code review-də diqqət yetirin: Context/View tipli statik sahələr, Activity-ni bağlayan anonim siniflər, lambdalar, əl ilə abunəliklər, composite olmadan RxJava disposable, Bundle vasitəsilə Fragment saxlanması. Kotlin-də əlavə olaraq korutinləri həyat dövrünə bağlı olmadan launch üçün yoxlayın.
LeakCanary test boru kəmərinin bir hissəsi kimi işləyə bilər: LeakCanary ilə qəbul testlərini işə salın və sızma tapılarsa, build-i uğursuz hesab edin. Bu, sızmaların istehsalata daxil olmasının qarşısını alır. Yoxlamanı StaticFieldLeak qaydası ilə Android Lint ilə tamamlayın — o, statik təhlil səviyyəsində potensial sızmaları aşkarlayır.
// Testlərdə LeakCanary
class LeakTest {
@Test
fun activityShouldNotLeak() {
ActivityScenario.launch(MainActivity::class.java)
.close()
LeakAssertions.assertNoLeak() // sızma varsa fail
}
}
Tez-tez verilən suallar
Sızma səbəbdir, OutOfMemoryError isə nəticədir. Bir sızma OOM-a gətirib çıxarmır, lakin onlarla sızmanın yığılması Heap-i tükəndirir. OOM ölümcül istisnadır, sızma isə zamanla ona gətirib çıxaran nümunədir.
Android Memory Profiler vasitəsilə: ekranı 5 dəfə açıb-bağlayın, hər bağlamadan sonra GC çağırın. Yaddaş baza səviyyəsinə qayıtmırsa — sızma var. Heap Dump götürün və siyahıda bağlamadan sonra sayı 0-dan çox olan Activity sinfini tapın.
Qismən. Kotlin null-təhlükəsizliyi problemini həll edir, lakin strong references-i idarə etmir. lifecycleScope və viewModelScope ilə korutinlər fon tapşırıqlarından sızmaların qarşısını alır, sealed class və data class isə sızmalara səbəb olan halların sayını azaldır. Əsas müdafiə — dil funksiyaları deyil, arxitektura nümunələridir.
LeakCanary bəzən yanlış müsbət (false positive) verir: obyekt müvəqqəti olaraq sistem tərəfindən saxlanıla bilər (məsələn, InputMethodManager son View-i saxlayır). Əl ilə yoxlayın: Retained Size < 1 KB və GC Root sistem xidmətidirsə, çox güman ki, bu yanlış siqnaldır.
Xeyr. Sızmalar GC olan istənilən platformada mümkündür: iOS (Swift/Objective-C), Flutter (Dart), veb-brauzerlər (JavaScript). Mexanizmlər eynidir — GC Root-dan strong reference. iOS-da ARC yaddaşı avtomatik idarə edir, lakin obyektlər arasında retain cycle eyni sızmanı yaradır.
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