Mobil tətbiqlərdə yaddaş sızması — bu nədir, səbəbləri və aşkarlama üsulları

Müəllif: IT Sectr Dərc olunub: 2026-03-29 Oxuma vaxtı: 9 dəq

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

  • Memory Leak — obyekt yaddaşda qalır, baxmayaraq ki, ona proqram məntiqindən aktiv istinadlar yoxdur
  • Statik istinadlar Activity və ya Context-ə — Android-də sızmaların ən çox yayılmış səbəbi
  • LeakCanary — Android-də sızmaları avtomatik aşkarlamaq üçün standart alət
  • WeakReference və Application Context — sızmaların qarşısını almaq üçün əsas texnikalar
  • Lifecycle-aware komponentlər abunəliklərlə bağlı sızmaların bütün sinfini aradan qaldırır

Yaddaş sızması nədir

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ı.

Mobil tətbiqlərdə tipik sızma növləri

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.

Activity və ya Context-ə statik istinad

Ə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.

kotlin
object MySingleton {
    private var weakActivity: WeakReference<Activity>? = null

    fun attach(activity: Activity) {
        weakActivity = WeakReference(activity)
    }
}

Gizli istinadlı daxili siniflər

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əlikdən çıxılmayan dinləyicilər və abunəliklər

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.

kotlin
// 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) }
}

Recycle olmadan Bitmap

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.

onDestroyView-dən sonra Fragment istinadı

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.

Yaddaş sızmasını necə aşkarlamaq olar

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.

Sızmaları aşkarlamaq üçün alətlər

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ətMetodNəticə formatı
LeakCanaryAvtomatik monitoringHeap Dump + sızmanın stack trace-i
Android Memory ProfilerƏl ilə monitoringYaddaş qrafiki + Heap Dump
MAT (Eclipse)Dərin təhlilDominator Tree hesabatı + GC Root yolu
PerfettoSistem səviyyəli traceZaman 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.

Yaddaş sızmalarının qarşısını necə almaq olar

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.

Güclü istinadlar qaydası

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.

Lifecycle-aware arxitektura

ViewModelLiveData 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.

GC Root-a diqqət yetirərək Code Review

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.

CI-da avtomatik yoxlama

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.

kotlin
// 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

Yaddaş sızması OutOfMemoryError-dan nə ilə fərqlənir?

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.

LeakCanary olmadan sızmanı necə tapmaq olar?

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.

Kotlin dil səviyyəsində sızmaların qarşısını ala bilərmi?

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.

Niyə LeakCanary olmayan sızmanı tapır?

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.

Yaddaş sızmaları yalnız Android-də olur?

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ə

  • Memory Leak — unudulmuş strong reference səbəbindən GC-nin boşalda bilmədiyi obyekt
  • Statik istinadlar Activity və Context-ə — sızmaların ən çox yayılmış səbəbi
  • Gizli istinadlar anonim siniflər, lambdalar və RxJava abunəlikləri vasitəsilə açıq olanlardan daha məkrlidir
  • LeakCanary sızmaları avtomatik tapır və dəqiq stack trace göstərir
  • Lifecycle-aware komponentlər (ViewModel, LiveData, lifecycleScope) sızmaların sinfini aradan qaldırır
  • Heap Dump və Retained Size təhlili — əl ilə diaqnostikanın əsas üsulu
  • Profilaktika strong reference üçün code review və LeakCanary CI yoxlamasını əhatə edir

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.

Layihəni müzakirə et

Həm də oxuyun