Stack Overflow — qo'ng'iroqlar stekining to'lib ketishi xatosi (java.lang.StackOverflowError), ip stekining maksimal chuqurligi oshib ketganda yuzaga keladi. Java Virtual Machine Specification ga ko'ra, JVM dagi odatdagi stek chuqurligi 64-bitli tizimlar uchun 1024 kadrdan iborat. Asosiy sabab — to'xtashning bazaviy sharti bo'lmagan cheksiz rekursiya.
Asosiy fikrlar
StackOverflowError — bu Java Virtual Machine (JVM) yoki Android Runtime (ART) ning halokatli xatosi bo'lib, ipning qo'ng'iroqlar steki maksimal ruxsat etilgan chuqurlikka yetganda yuzaga keladi. OutOfMemoryError dan (Heap yetishmasligi) farqli o'laroq, StackOverflowError xotiraning boshqa sohasi — metod chaqiruvi kadrlari va lokal o'zgaruvchilar saqlanadigan stek bilan bog'liq.
Har bir metod chaqiruvi stekda kadr yaratadi: qaytish manzili, parametrlar va lokal o'zgaruvchilar. Metoddan qaytganda kadr yo'q qilinadi. Agar metod o'zini chaqirsa (rekursiya) bazaviy shartsiz, kadrlar stek to'lguncha yig'iladi. JVM yangi kadr ajrata olmaydi va «null» xabari bilan (Java da) yoki cheksiz takrorlanuvchi stek qatori ko'rsatilgan holda StackOverflowError ni tashlaydi.
Ip stekining o'lchami yaratilganda belgilanadi va bajarilish vaqtida o'zgarmaydi. Android da asosiy ip stekining odatdagi o'lchami 32–48 KB ni tashkil etadi, bu ko'p sonli lokal o'zgaruvchilarga ega bo'lmagan metodlar uchun taxminan 512–1024 kadr chuqurligini beradi. Fon iplari uchun standart o'lcham kichikroq — 16–24 KB.
Qo'ng'iroqlar steki (Call Stack) — metodlarning bajarilish tartibini boshqaruvchi LIFO (Last In, First Out) ma'lumotlar strukturasidir. Har safar dastur metodni chaqirganda, JVM stekda kadr yaratadi va uni tepaga joylashtiradi. Metod tugaganda kadr olib tashlanadi.
Har bir kadr o'z ichiga oladi: operand stack (baytkod ko'rsatmalari uchun operandlar steki), array of local variables (this ni o'z ichiga olgan holda), reference to constant pool va qaytish manzili. Metod qancha ko'p lokal o'zgaruvchilarga ega bo'lsa, uning kadr o'lchami shuncha katta bo'ladi va stek to'lguncha shuncha kam metod chaqirilishi mumkin. 10 parametrli va 20 lokal o'zgaruvchili metod parametrsiz metodga qaraganda taxminan 3 baravar ko'p joy egallaydi.
Android da ART Desktop JVM dan farqli ravishda o'zining stek tatbiqidan foydalanadi. ART ma'lum chegaralarda stekni dinamik ravishda oshirishi mumkin, ammo har bir ip uchun baribir qattiq chegara mavjud. Asosiy ip (UI ip) eng katta stekka ega, chunki Activity ning butun hayot siklisi va hodisalarni qayta ishlash unda bajariladi.
// StackOverflowError ga olib keladigan rekursiya
fun recursiveCall(depth: Int): Int {
return recursiveCall(depth + 1) // bazaviy shart yo'q
}
// Chaqiruv ~1000 chuqurlikda StackOverflowError ga olib keladi
recursiveCall(0)
Besh tipik stsenariy mobil ilovalarda StackOverflowError ga olib keladi. Ularning aksariyati rekursiya bilan bog'liq, ammo kamroq ravshan sabablar ham mavjud.
Eng keng tarqalgan sabab. Dasturchi to'xtash sharti bo'lmagan yoki hech qachon true bo'lmaydigan shartga ega rekursiv metod yozadi. Har bir chaqiruv kadr qo'shadi va stek kadr o'lchamiga qarab 500–2000 iteratsiyadan so'ng to'ladi. Odatdagi misol: n == 0 ni tekshirmasdan n! faktorialini hisoblash.
Har bir rekursiv metodning boshida bazaviy shartni tekshiring. Kotlin da parametrlarni boshlanishda tekshirish uchun require() yoki check() dan foydalaning. Chuqur rekursiya uchun (100 darajadan ko'p) iterativ yondashuv bilan almashtirishni ko'rib chiqing.
A sinfi B nusxasini yaratadi, B sinfi A nusxasini yaratadi — bu konstruktorlarda tsiklik bog'liqlikdir. A ni yaratishga urinishda B ning konstruktori chaqiriladi, B A ning konstruktorini chaqiradi va shu tarzda StackOverflowError gacha davom etadi. DI freymvorklari (Dagger, Hilt) bunday tsikllarni kompilyatsiya bosqichida aniqlaydi, ammo ob'ektlarni qo'lda yaratish ularni tutmaydi.
Bog'liqlik graflari bilan Dependency Injection dan foydalaning: Dagger yoki Koin tsikllarni qurish bosqichida tekshiradi. Agar tsikl muqarrar bo'lsa, to'g'ridan-to'g'ri bog'liqlikni kechiktirilgan initsializatsiyali interfeys yoki Provider fabrikasi bilan almashtiring.
// Tsiklik bog'liqlik — StackOverflowError
class A(private val b: B)
class B(private val a: A)
// Kechiktirilgan yechim
class A(private val bProvider: Provider<B>)
View daraxtini (ViewGroup.getChildAt()), fayl tizimini yoki JSON strukturasini rekursiya orqali aylanib o'tish 500–1000 elementdan ortiq chuqurlikda stek chegarasidan oshib ketishi mumkin. 20 darajali ichki joylashtirilgan Android ViewGroup kam uchraydi, ammo 2000 ta ichki joylashtirilgan ob'ektli JSON ni rekursiv tahlil qilish real stsenariydir.
Rekursiv aylanib o'tishni aniq Stack<T> yoki ArrayDeque orqali iterativ bilan almashtiring. Bu stek to'lib ketishi xavfini butunlay bartaraf etadi, chunki yig'indagi ob'ektlar stek chegarasi bilan cheklanmaydi. Queue orqali BFS (Breadth-First Search) ham muammoni hal qiladi.
Android-ga xos sabab: konfiguratsiyani noto'g'ri qayta ishlashda hayot siklisi metodlarining tsiklik chaqiruvi. Masalan, onConfigurationChanged ichida recreate() chaqiriladi, u yana onConfigurationChanged ni chaqiradi va bu StackOverflowError gacha davom etadi. Xuddi shunday: onLayout() ichidagi setContentView(), qayta o'lchash va layout ga sabab bo'ladi.
Konfiguratsiya o'zgarishi bilan bog'liq metodlar ichida recreate() ni chaqirmang. Mavzu o'zgarishida UI ni yangilash uchun recreate siz setTheme() dan foydalaning. Dinamik orientatsiya o'zgarishi uchun — konfiguratsiyada flagsiz, bir martalik requestOrientation().
Gson, Moshi yoki Kotlin Serialization tsiklik havolalarga ega ob'ektni (A B ga havola qiladi, B A ga havola qiladi) serializatsiya qilishga urinishda cheksiz rekursiyaga ketadi va StackOverflowError bilan qulab tushadi. Bu bidirectional Relationship (JPA, ForeignKey bilan Room) bilan Entity ni serializatsiya qilishda tez-tez uchraydigan muammodir.
Tsiklning bir tomoni uchun @Transient, @JsonIgnore yoki @kotlinx.serialization.Transient dan foydalaning. Gson uchun — aniq chuqurlik cheklovi bilan JsonSerializer. Room uchun — Entity ni hech qachon to'g'ridan-to'g'ri serializatsiya qilmang, DTO mappers dan foydalaning.
StackOverflowError diagnostikasi boshqa xotira xatolariga qaraganda soddaroq: stack trace ko'p hollarda takrorlanuvchi chaqiruvlar ketma-ketligini ko'rsatadi. Bu darhol rekursiyaga ishora qiladi.
StackOverflowError ning stack trace i noyobdir: dastlabki 200–500 qatordan so'ng bir xil chaqiruv naqshining takrorlanishi boshlanadi. JVM oxirida takrorlanuvchi qatorlarni qisqartiradi va «... 1234 more» ni ko'rsatadi. «...» oldidagi takrorlanmaydigan qatorlar soni xatoga olib kelgan rekursiya chuqurligini ko'rsatadi.
Stack trace ning birinchi qatorlarini o'qing — ular takrorlanish qaysi metoddan boshlanganini ko'rsatadi. O'zini chaqiradigan yoki unga qaytadigan chaqiruv zanjirini yaratadigan metodni toping. Bazaviy shartni tuzating yoki rekursiyani sikl bilan almashtiring.
Vaqtinchalik muammoni JVM -Xss flagi orqali stek o'lchamini oshirish bilan hal qilish mumkin. Android da stek o'lchami AndroidManifest orqali o'rnatiladi: android:largeHeap stekka ta'sir qilmaydi. Kodda ip stekini oshirish uchun: Thread(ThreadGroup, Runnable, name, stackSize). stackSize — baytlardagi istalgan o'lcham.
// Kattalashtirilgan stek bilan ip yaratish
val thread = Thread(null, runnable, "big-stack-thread", 64 * 1024)
thread.start()
Muhim: stekni oshirish muammoni hal qilmaydi, balki uni kechiktiradi. 10 000 darajali rekursiyada 64 KB stek 128 KB stek bilan almashtiriladi, bu 20 000 darajani beradi — ammo xato baribir sodir bo'ladi, faqat keyinroq. Yagona to'g'ri yechim — rekursiyani iterativ almashtirish.
Iterativ algoritmlar oraliq holatlarni saqlash uchun qo'ng'iroqlar stekidan foydalanmaydi — ularni yig'inda saqlaydi (Stack<T> yoki ArrayDeque). Ikkilik daraxtni aylanib o'tish, faktorial hisoblash, Fibonachchi — istalgan rekursiyani aniq stek orqali iteratsiyaga aylantirish mumkin.
// Daraxtni iterativ aylanib o'tish — StackOverflow xavfisiz
fun traverseIterative(root: Node?) {
val stack = ArrayDeque<Node>()
stack.push(root)
while (stack.isNotEmpty()) {
val node = stack.pop() ?: continue
process(node)
node.right?.let { stack.push(it) }
node.left?.let { stack.push(it) }
}
}
StackOverflowError ning profilaktikasi — potentsial rekursiv tsikllarni ishlab chiqarishga tushishidan oldin aniqlaydigan qoidalar va vositalar to'plamidir.
Debug yig'ilishida rekursiv metodlarga himoya chuqurlik hisoblagichini qo'shing. Agar chuqurlik chegaradan oshsa (masalan, 1000), tushunarli xabar bilan istisno tashlang. Bu o'qib bo'lmaydigan trace bilan StackOverflow Error ni tushunarli biznes istisnosiga aylantiradi.
fun safeRecursive(n: Int, depth: Int = 0): Int {
if (depth > 1000) {
throw IllegalStateException("Rekursiya 1000 darajadan oshib ketdi")
}
return if (n <= 1) n
else safeRecursive(n - 1, depth + 1)
}
Detekt (Kotlin) va Infer (Facebook) statik tahlil darajasida potentsial cheksiz rekursiyalarni topadi. Detekt parametrlarni o'zgartirmasdan self-call haqida ogohlantiruvchi PotentiallyInfiniteRecursion qoidasiga ega. Uni CI qoidalar to'plamiga qo'shing va severity ni error ga o'rnating.
Code review da quyidagilarga e'tibor bering: har qanday self-call metodlarga, lambda ichidagi rekursiv chaqiruvlarga (Kotlin inline funktsiyalari), turli sinflar orasidagi tsiklik chaqiruvlarga, property delegates dagi rekursiyaga. Har bir rekursiv metod uchun tekshiring: bazaviy shart bormi, parametr har qadamda o'zgaradimi, parametrning o'zgarishi bazaviy shartga erishishni kafolatlaydimi.
Kotlin tailrec modifikatorini qo'llab-quvvatlaydi: agar rekursiv metod tailrec bilan belgilangan bo'lsa va chaqiruv dum bo'lsa (oxirgi operatsiya), kompilyator uni iteratsiyaga aylantiradi. Biroq tailrec faqat self-call uchun ishlaydi (metod to'g'ridan-to'g'ri o'zini chaqiradi), o'zaro rekursiya uchun ishlamaydi va Android ga mos Kotlin versiyalarida 1.5 gacha qo'llab-quvvatlanmaydi.
tailrec fun factorial(n: Int, acc: Int = 1): Int {
return if (n <= 1) acc
else factorial(n - 1, acc * n) // dum chaqiruvi
}
Tez-tez so'raladigan savollar
Mumkin, ammo faqat Java darajasida. Error, Exception kabi, Throwable dir. Biroq StackOverflowError dan so'ng stek shikastlangan — sig'magan kadrlar to'g'ri yakunlana olmaydi. Catch blokida yangi ob'ekt yaratish urinishi qayta StackOverflowError ga sabab bo'lishi mumkin.
Asosiy ip uchun — 32–48 KB, fon ipi uchun — 16–24 KB. Aniq o'lcham Android versiyasi va qurilma ishlab chiqaruvchisiga bog'liq. ART dinamik stek kengaytirishdan foydalanadi, ammo boshlang'ich qiymatdan 2× dan ko'p emas.
Kotlin da — ha, agar metod tailrec bilan belgilangan bo'lsa. Kompilyator dum rekursiyasini iteratsiyaga aylantiradi, stek o'sishini butunlay bartaraf etadi. Java da dum rekursiyasi JVM tomonidan optimallashtirilmaydi (Scala kabi funksional tillardan farqli ravishda).
Stek o'lchami emulyator va real qurilmada farq qilishi mumkin. Emulyator odatdagi steki 512–1024 KB bo'lgan Desktop JVM dan foydalanadi, Android ART esa — 32–48 KB. Xato ART da Desktop JVM dagidan oldinroq namoyon bo'ladi.
Xotira sohasi: StackOverflowError — stek xatosi (chaqiruv kadrlari), OutOfMemoryError — yig'in xatosi (ob'ektlar). StackOverflowError deyarli har doim rekursiya natijasida yuzaga keladi, OutOfMemoryError esa — xotira oqishi yoki katta ob'ektlar natijasida.
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.