Xotira sizib ketishi (memory leak) — ilova endi kerak bo'lmagan ob'ektlar egallagan xotirani bo'shatmasligi holati. Mobil ishlanmada bu ayniqsa muhim: cheklangan heap va swap yo'qligi OutOfMemoryError va ilovaning ishdan chiqishiga olib keladi. Purdue University ma'lumotlariga ko'ra (2022), Google Play-dagi Android ilovalarining 35% kamida bitta xotira sizib ketishiga ega. Odatdagi stsenariylar, diagnostika vositalari va bartaraf etish usullarini ko'rib chiqamiz.
Asosiy fikrlar
Xotira sizib ketishi — ob'ekt dastur uchun endi kerak bo'lmagandan keyin ajratilgan xotira tizimga qaytarilmasligi holati. Axlat yig'uvchi bunday ob'ektni tirik deb hisoblaydi, chunki unga GC Root-dan faol havola zanjiri bor.
Java/Kotlin-da axlat yig'uvchi avtomatik ishlaydi, lekin ob'ektga texnik havola mavjud bo'lsa, uning mantiqan kerak emasligini aniqlay olmaydi. Dasturchi keraksiz aloqalarni ochiqdan-ochiq uzishi kerak. Swift/Objective-C-da ARC avtomatik ravishda havolalarni sanaydi, lekin retain cycles hisoblagichni nolga tushirishni bloklaydi.
Sizib ketishlarning asosiy xavfi — kumulyativ effekt. Har bir sizib ketish oz miqdordagi xotirani iste'mol qiladi, lekin ekranlar o'rtasida ko'p marta o'tishlarda (ekran aylanishlari, Activity ochish/yopish) sizib ketishlar to'planadi va heap limiti tugaguncha davom etadi.
Sizib ketish — ob'ekt kod uchun mavjud emas, lekin GC tomonidan o'chirilmagan. Shishish — ob'ekt mantiqan kerak, lekin ortiqcha miqdorda saqlanadi. Shishish misoli: 30 MB ish to'plami bilan 100 MB-lik rasm keshi. Ikkala muammo OOM-ga olib keladi, lekin sabablar va davolash usullari farq qiladi.
ART (Android Runtime) concurrent compaction bilan avlodga asoslangan axlat yig'ishdan foydalanadi. Xotira yosh avlod (Young), qari (Old) va ulkan ob'ektlarga (Large) bo'linadi. Bir necha GC siklidan o'tgan ob'ektlar Old generation-ga ko'chiriladi, bu erda yig'ish kamroq sodir bo'ladi — bu oddiy sikllarni tezlashtiradi.
GC heap ma'lum bir bandlik chegarasiga yetganda boshlanadi (odatda 75-85%). GC vaqtida ilovaning barcha oqimlari to'xtatiladi (STW — Stop The World). Qancha ko'p tirik ob'ekt bo'lsa, pauza shuncha uzoq bo'ladi. Sizib ketishlar tirik ob'ektlar sonini ko'paytirib, GC pauzalarini uzaytiradi.
Axlat yig'uvchi tirik ob'ektlarni GC Roots-dan boshlab grafni aylanib aniqlaydi: statik maydonlar, faol oqimlarning stek o'zgaruvchilari, JNI havolalari. Bu ildizlardan havolalar orqali erishish mumkin bo'lgan har qanday ob'ekt tirik deb hisoblanadi — hatto dasturchi uning endi kerak emasligini bilsa ham.
// Misol: statik kolleksiya GC Root sifatida — doimiy sizib ketish
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 bloklamaydi — to'g'ri xatti-harakat
}
}
WeakReference muammoni hal qiladi: GC tirik ob'ektlarni aniqlashda weak havolalarni e'tiborsiz qoldiradi. Agar ob'ektga faqat weak havolalar qolgan bo'lsa, u keyingi GC siklida yig'iladi.
Activity Context — Android-dagi eng ommaviy sizib ketish stsenariysi. Agar singleton, statik maydon yoki uzoq muddatli xizmat Activity Context-ga havolani saqlasa, butun Activity barcha View-lar bilan GC tomonidan yig'ila olmaydi. Yechim: uzoq muddatli ob'ektlar uchun Application Context-dan foydalaning.
Handler va yuborilgan xabarlar — Handler.postDelayed(runnable, delay) Main Looper navbatiga xabar qo'yadi. Agar Activity kechikish muddati tugamagan holda yo'q qilinsa, xabar hali ham navbatda va Runnable → anonim sinf → tashqi sinf (Activity) orqali havolani ushlab turadi.
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) // majburiy: navbatni tozalang
super.onPause()
}
}
Inner Classes — statik bo'lmagan ichki sinf tashqi sinf namunasiga yashirin havolaga ega. Agar tashqi sinf Activity bo'lsa va ichki sinf tashqariga uzatilgan bo'lsa (masalan, RecyclerView.Adapter-ga), Activity yig'ila olmaydi.
Android Studio Memory Profiler — real vaqtda heap-ni kuzatish uchun o'rnatilgan vosita. Band qilingan xotira grafigini, ajratmalar sonini va turlar bo'yicha ob'ektlarni ko'rsatadi. Heap dump yozib olish va MAT-da tahlil qilish uchun HPROF formatiga eksport qilish imkonini beradi.
Eclipse MAT (Memory Analyzer Tool) — heap dump-ning ish stoli analizatori. Avtomatik ravishda Leak Suspects Report-ni yaratadi, eng katta retained size-ga ega ob'ektlarni ajratib ko'rsatadi va har bir shubhali ob'ekt uchun taxminiy GC root zanjirini taklif qiladi.
Xcode Memory Graph Debugger — iOS uchun. Ilovani to'xtatadi va ob'ekt grafigini vizuallashtiradi. Retain cycles qizil rang bilan ajratiladi, istalgan ob'ektni bosib uning retain count va havolalarini ko'rish mumkin.
| Vosita | Imkoniyatlar | Murakkablik |
|---|---|---|
| Memory Profiler | Real-vaqt grafigi, heap dump, ob'ekt ajratishni kuzatish | Past |
| Eclipse MAT | Dominator daraxti, sizib ketish gumondorlari, OQL so'rovlari | O'rta |
| LeakCanary | Avtomatik aniqlash, bildirishnomadagi sizib ketish izi | Minimal |
| Xcode Memory Graph | Retain cycles vizual grafi, tirik ob'ektlar ro'yxati | Past |
Uber Engineering Blog ma'lumotlariga ko'ra, CI/CD quvurida avtomatik xotira profillashni (LeakCanary + heap dump tahlili) joriy etish ishlab chiqarishdagi xotira bilan bog'liq hodisalar sonini 3 oy ichida 60% ga kamaytiradi.
Context-ni almashtirish — agar ob'ekt Activity-dan uzoq yashasa, applicationContext-dan foydalaning. Barcha uzoq muddatli ob'ektlar (singletonlar, repozitoriylar, database helpers) Application Context olishi kerak, Activity Context emas. Istisno: Activity uchun xos bo'lgan mavzu yoki resurslarga kirishga ehtiyoji bo'lgan UI komponentlari.
Lifecycle-aware komponentlar — LifecycleObserver, DefaultLifecycleObserver yoki reactivex dan foydalanish onDestroy vaqtida obunalarni avtomatik bekor qiladi. Android Jetpack lifecycleScope va viewModelScope-ni taqdim etadi, ular tegishli hodisa bilan tozalanadi.
Static inner class — agar ichki sinf tashqi sinf maydonlariga kirishga muhtoj bo'lmasa, uni static qiling. Statik ichki sinf tashqi sinfga yashirin havolaga ega emas. Agar kirish kerak bo'lsa, aniq havola uchun WeakReference dan foydalaning.
class MyActivity : AppCompatActivity() {
// ❌ Statik bo'lmagan ichki sinf — MyActivity ga yashirin havola
inner class BadListener : SomeListener {
override fun onEvent() { /*...*/ }
}
// ✅ Statik ichki sinf — yashirin havola yo'q
class GoodListener(private val activityRef: WeakReference<MyActivity>) : SomeListener {
override fun onEvent() { /*...*/ }
}
}
iOS-da capture list-lardan foydalaning: yaratuvchidan uzoq yashashi mumkin bo'lgan yopilishlarda [weak self]. Delegate-lar uchun zaif havolalardan foydalaning (weak var delegate). Faqat self hayoti davomida chaqirilishiga kafolat bo'lgan yopilishlar uchun [unowned self] ishlatilishi mumkin, ammo ehtiyot bilan — bo'shatilgan ob'ektga murojaat qilish crash-ga olib keladi.
Tez-tez so'raladigan savollar
Android-da ekranlar o'rtasida bir necha marta o'ting (Activity A → B → A → B) va adb shell dumpsys meminfo package_name ni tekshiring. Agar Total PSS barqaror o'sib, dastlabki qiymatga qaytmasa — sizib ketish bor. iOS-da xuddi shunday: vizual tekshirish uchun Xcode-da Debug Memory Graph dan foydalaning.
Ha, agar CoroutineScope komponent yo'q qilinganda bekor qilinmasa. GlobalScope-da ishga tushirilgan korutina Activity-ning finish() dan keyin ham bajarilishda davom etadi. Yechim: viewModelScope (onCleared da bekor qilinadi) yoki lifecycleScope (onDestroy da bekor qilinadi) dan foydalaning. O'z Scope-laringiz uchun LifecycleOwner orqali lifecycle-aware scopes yarating.
Bitmap piksel ma'lumotlarini Java heap-da emas, balki native heap-da saqlaydi. Bu shuni anglatadiki, Java GC Bitmap-ning haqiqiy hajmini ko'rmaydi. Agar Bitmap-da recycle() chaqirilmasa yoki havola nolga tushirilmasa, native xotira bo'shamaydi. Kichiklashtirilgan nusxalarni yuklash uchun BitmapFactory dan inSampleSize bilan va keshlarni avtomatik boshqarish uchun Glide/Coil dan foydalaning.
Statik maydon — bu GC Root. Sinf yuklangan ekan, yashaydi (Android-da — Process yashagan ekan). Agar statik maydon Activity, Bitmap, View yoki boshqa og'ir ob'ektga havola qilsa, bu ob'ekt hech qachon GC tomonidan yig'ilmaydi. Statik maydon — abadiy havola. Yechim: faqat WeakReference saqlang yoki onDestroy da statik maydonni nolga tushiring.
ARC kuchli havolalar soni nolga tushganda ob'ektlarni avtomatik bo'shatadi. Retain cycle — ARC-da sizib ketishning yagona yo'li. Har doim parent→child havolalari uchun weak dan foydalaning, bu erda child parent-dan uzoq yashashi kerak (delegatlar, data source). Yopilishlar uchun capture list [weak self] dan foydalaning va yopilish ichida self-ni nil ga tekshiring.
Xulosa
Biz kalit topshirig'i bilan mobil ilovani ishlab chiqamiz
IT Sectr 2017-yildan beri startaplar va korxonalar uchun iOS va Android ilovalarini yaratadi. Biz sizga maslahat beramiz va eng yaxshi yechimni taklif qilamiz.
Shuningdek o'qing