Xotira sizib ketishi (Memory Leak) — ilova endi kerak bo'lmagan obyektlarga havolalarni ushlab, axlat yig'uvchiga (GC) occupied memory-ni bo'shatishga to'sqinlik qiladigan holat. LeakCanary ma'lumotlariga ko'ra, hatto yaxshi yozilgan ilovalarda ham 10 000 kod satriga 3–5 sizib ketish uchraydi. Har bir sizib ketish asta-sekin mavjud xotirani kamaytiradi, sekinlashishga va OutOfMemoryError-ga olib keladi.
Asosiy fikrlar
Xotira sizib ketishi (Memory Leak) — obyekt kuchli havolalar (Strong Reference) zanjiri orqali erishish mumkin bo'lib qoladigan, garchi mantiqan ilova uchun endi kerak bo'lmasa ham. Axlat yig'uvchi (GC) bunday obyektni tirik deb hisoblaydi va u egallagan xotirani bo'shatmaydi. Natijada, mavjud yig'in (Heap) xotirasi doimiy ravishda kamayadi va GC pauzalarining chastotasi ortadi.
Farqli o'laroq qo'lda xotira boshqaruvi bo'lgan tillardan (C, C++), Java/Kotlin-da sizib ketish unutilgan free() emas, balki unutilgan havoladir. Ildiz obyektdan (GC Root) sizib ketayotgan obyektgacha strong reference mavjud ekan, GC uni kerakli deb hisoblaydi. Odatdagi GC Root: statik maydonlar, faol oqimlar, chaqiruv steki, JNI-global havolalar.
Sizib ketishlarning xavfi — ularning yig'ilish effekti. 100 KB lik bitta sizib ketish sezilmaydi, ammo 100 ta bunday sizib ketish 10 MB ni egallaydi va ilova tez-tez GC tufayli sekinlasha boshlaydi. Kritik sizib ketish massasi OutOfMemoryError va ilovaning ishdan chiqishiga olib keladi. Sizib ketish alomatlari: Profiler grafigida xotira iste'molining doimiy o'sishi, STW (Stop The World) bilan tez-tez GC pauzalari va UI unumdorligining pasayishi.
Besh turdagi sizib ketish mobil ishlanmada 95% hollarni qamrab oladi. Har birining o'z sababi va kodda xarakterli namunasi bor.
Eng taniqli Android sizib ketishi — Activity yoki Context-ga statik havolani saqlash. Odatdagi kod: onDestroy() vaqtida nolga tushirilmaydigan statik Activity maydoni. Statik maydon tirik ekan, 1–10 MB egallashi mumkin bo'lgan View daraxti bilan butun Activity tirik qoladi. Bu LeakCanary birinchi navbatda topadigan klassik sizib ketishdir.
Yechim: Activity yoki Context-ni hech qachon statik maydonlarda saqlamang. Activity-dan uzoqroq yashovchi singletonlar uchun Application Context dan foydalaning. Activity-ga havola kerak bo'lsa — WeakReference<Activity> dan foydalaning.
object MySingleton {
private var weakActivity: WeakReference<Activity>? = null
fun attach(activity: Activity) {
weakActivity = WeakReference(activity)
}
}
Anonim sinflar va statik bo'lmagan ichki sinflar yashirin ravishda o'z ichiga olgan sinfga havolani saqlaydi. onDestroy() dan keyin bajariladigan Handler-ga uzatilgan Runnable butun Activity-ni ushlab turadi. Activity-ni yopadigan Retrofit callback ham xuddi shunday qiladi. Bu sizib ketishning eng makkor turi — yashirin havola kodda ko'rinmaydi.
Kotlin object ifodalari va lambdalar ham tashqi sinfga havolalarni tutib oladi. Ichki sinflarni statik (yoki Kotlin-da top-level) qiling va tashqi havolalarni WeakReference orqali uzating. Lambdalar uchun viewLifecycleOwner bilan Lifecycle-aware yondashuvidan foydalaning.
Obuna tizim xizmatlariga obunadan chiqmasdan — to'g'ridan-to'g'ri sizib ketish. onResume() da ro'yxatdan o'tgan, lekin onPause() da unregister chaqirilmagan SensorManager, LocationManager, NotificationListener Activity-ni ushlab turadi. Xuddi shunday: CompositeDisposable-ga qo'shilmagan RxJava Disposable va GlobalScope orqali ishga tushirilgan korutin.
Lifecycle-aware komponentlardan foydalaning: LifecycleOwner bilan observe() onDestroy() vaqtida avtomatik obunadan chiqadi. RxJava uchun — viewLifecycleOwner.lifecycle.addObserver DisposableObserver bilan. Korutinlar uchun — lifecycleScope.launch() hayotiy tsiklga bog'langan.
// Lifecycle orqali avtomatik obunadan chiqish
viewModel.userData.observe(viewLifecycleOwner) { data ->
updateUI(data)
}
// lifecycleScope bilan korutinlar
lifecycleScope.launch {
viewModel.loadData().collect { render(it) }
}
Bitmap sezilarli yig'in xotirasini egallaydi: bitta FullHD-bitmap — 1920 × 1080 × 4 bayt = 8.3 MB. Agar ro'yxatning har bir elementi uchun Bitmap yaratilsa va yashirishda recycle() chaqirilmasa, xotira tez tugaydi. Eski Android versiyalarida (3.0 dan oldin) Bitmap native xotira saqlanardi, ammo zamonaviylarida — Dalvik/ART yig'inida va GC uni faqat strong reference bo'lmaganda bo'shata oladi.
Rasmlarni yuklash uchun Glide yoki Coil dan foydalaning — bu kutubxonalar keshlash va recycle-ni avtomatik boshqaradi. To'g'ridan-to'g'ri Bitmap bilan ishlasangiz, endi ko'rsatilmaydigan katta rasmlar uchun bitmap.recycle() ni chaqiring va kichraytirilgan nusxalarni yuklash uchun inSampleSize dan foydalaning.
Fragment ikki hayotiy tsiklga ega: Fragment ning o'zi va uning View si. onDestroyView() dan keyin View daraxti yo'q qilinadi, ammo tashqi havola mavjud bo'lsa, Fragment ning o'zi xotirada qolishi mumkin. Odatdagi xato — yo'q qilinganda tozalanmaydigan ViewPager adapterida yoki navigatsiya grafigida Fragment-ga havolani saqlash.
Hech qachon Fragment-ga havolani uzoq umr ko'ruvchi obyektlarning maydonlarida saqlamang. Ichki fragmentlar uchun childFragmentManager va ular orasida ma'lumot uzatish uchun LifecycleOwner bilan observe() dan foydalaning. ViewPager2 bu muammoni API darajasida hal qildi: FragmentTransactionAdapter hayotiy tsiklni to'g'ri boshqaradi.
Aniqlash ikki faktni tekshirishni talab qiladi: xotira expected lifetime dan keyin qaytmaydi va ma'lum turdagi obyektlar soni kamaymasdan ortadi. Tashxis jarayoni uch bosqichdan iborat.
Birinchi bosqich — Android Studio da Memory Profiler orqali vizual tekshirish. Memory yorlig'ini oching, maqsadli harakatni bajaring (ekranni oching va yoping), GC (Garbage Collection) tugmasini bosing va xotiraning boshlang'ich darajaga qaytganini tekshiring. Agar 3–4 ochish-yopish siklidan keyin xotira barqaror o'ssa — sizib ketish bor.
Ikkinchi bosqich — Heap Dump olish. Memory Profiler da Dump Java Heap tugmasini bosing. Olingan .hprof faylini Android Studio da oching: yig'indagi barcha obyektlarni o'lchamlari va havolalari bilan ko'rasiz. Ekran yopilgandan keyin soni nol bo'lishi kerak bo'lgan sinflarni qidiring. Masalan, yopilgandan keyin soni 2 bo'lgan MainActivity — aniq sizib ketish.
Uchinchi bosqich — Retained Size va GC Root tahlili. Android Studio da Retained Size ni tahlil qiling: ushbu obyektni o'chirish qancha xotirani bo'shatadi. GC Root dan obyektgacha bo'lgan yo'l uni nima ushlab turganini ko'rsatadi: Static field → HashMap → Activity — va siz sizib ketish nuqtasini ko'rasiz. Reference widget paneli obyektning barcha egalarini ko'rsatadi.
To'rt vosita sizib ketishlarni aniqlashni avtomatik aniqlashdan chuqur Heap Dump tahliligacha qamrab oladi.
| Vosita | Usul | Natija formati |
|---|---|---|
| LeakCanary | Avtomatik monitoring | Heap Dump + sizib ketish stack trace |
| Android Memory Profiler | Qo'lda monitoring | Xotira grafigi + Heap Dump |
| MAT (Eclipse) | Chuqur tahlil | Dominator Tree hisoboti + GC Root yo'li |
| Perfetto | Tizim darajasida trace | Vaqt shkalasi + native xotira |
LeakCanary — har bir Android loyihasi uchun must-have. Activity/Fragment hayotiy tsikli tugagandan keyin sizib ketishlarni avtomatik aniqlaydi va sizib ketishning aniq joyini stack trace bilan ko'rsatadi. Integratsiya: build.gradle da bir satr. LeakCanary 2.x qo'lda ishga tushirishni talab qilmaydi — avtomatik ravishda Application Watcher ni ro'yxatdan o'tkazadi.
Profilaktika har bir bosqichda kodni tekshiruvchi qoidalar va vositalar to'plami orqali ishlab chiqish jarayoniga o'rnatiladi.
Hech qachon Activity, Fragment yoki View ga havolani statik maydonda, singletonda yoki uzoq umr ko'ruvchi obyektda saqlamang. Havolasiz iloji bo'lmasa — WeakReference dan foydalaning yoki ma'lumotlarni aynan qancha kerak bo'lsa shuncha yashaydigan va View ni to'g'ridan-to'g'ri saqlamaydigan ViewModel orqali saqlang.
ViewModel va LiveData Android Architecture Components dan hayotiy tsikl muammosini arxitektura darajasida hal qiladi. ViewModel ekran aylanishidan omon qoladi va View ga havolalarni o'z ichiga olmaydi. LiveData onDestroy() vaqtida observer ni avtomatik obunadan chiqaradi. Tizim xizmatlariga qo'lda obuna bo'lish o'rniga ulardan foydalaning.
Code review da quyidagilarga e'tibor bering: Context/View turidagi statik maydonlar, Activity-ni yopadigan anonim sinflar, lambdalar, qo'lda obunalar, compositesiz RxJava disposable, Bundle orqali Fragment saqlash. Kotlin da qo'shimcha ravishda korutinlarni hayotiy tsiklsiz launch uchun tekshiring.
LeakCanary test quvurining bir qismi sifatida ishlashi mumkin: LeakCanary bilan qabul testlarini ishga tushiring va sizib ketish topilsa, build ni muvaffaqiyatsiz deb belgilang. Bu sizib ketishlarning ishlab chiqarishga kirishini oldini oladi. StaticFieldLeak qoidasi bilan Android Lint tekshiruvini to'ldiring — u statik tahlil darajasida potentsial sizib ketishlarni topadi.
// Testlarda LeakCanary
class LeakTest {
@Test
fun activityShouldNotLeak() {
ActivityScenario.launch(MainActivity::class.java)
.close()
LeakAssertions.assertNoLeak() // sizib ketish bo'lsa fail
}
}
Tez-tez beriladigan savollar
Sizib ketish sabab, OutOfMemoryError esa natijadir. Bitta sizib ketish OOM ga olib kelmaydi, ammo o'nlab sizib ketishlarning to'planishi Heap ni tugatadi. OOM halokatli istisno, sizib ketish esa vaqt o'tishi bilan unga olib keladigan naqshdir.
Android Memory Profiler orqali: ekranni 5 marta oching va yoping, har bir yopishdan keyin GC ni chaqiring. Xotira bazaviy darajaga qaytmasa — sizib ketish bor. Heap Dump oling va ro'yxatda yopilgandan keyin soni 0 dan ko'p bo'lgan Activity sinfini toping.
Qisman. Kotlin null-xavfsizlik muammosini hal qiladi, ammo strong references ni boshqarmaydi. lifecycleScope va viewModelScope bilan korutinlar fon vazifalaridan sizib ketishlarning oldini oladi, sealed class va data class esa sizib ketishga olib keladigan holatlar sonini kamaytiradi. Asosiy himoya — til funksiyalari emas, arxitektura naqshlaridir.
LeakCanary ba'zan noto'g'ri ijobiy (false positive) beradi: obyekt vaqtincha tizim tomonidan ushlab turilishi mumkin (masalan, InputMethodManager oxirgi View ni ushlab turadi). Qo'lda tekshiring: agar Retained Size < 1 KB va GC Root tizim xizmati bo'lsa, bu noto'g'ri signal bo'lishi mumkin.
Yo'q. Sizib ketishlar GC bo'lgan har qanday platformada mumkin: iOS (Swift/Objective-C), Flutter (Dart), veb-brauzerlar (JavaScript). Mexanizmlar bir xil — GC Root dan strong reference. iOS da ARC xotirani avtomatik boshqaradi, ammo obyektlar orasidagi retain cycle bir xil sizib ketishni yaratadi.
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.