Stack Overflow — error sa pag-apaw ng call stack (java.lang.StackOverflowError), na nagaganap kapag lumampas sa maximum na lalim ng stack ng thread. Ayon sa Java Virtual Machine Specification, ang tipikal na lalim ng stack sa JVM ay 1024 na frame para sa 64-bit system. Ang pangunahing dahilan — walang hanggang recursion na walang base na kondisyon ng paghinto.
Mga pangunahing punto
StackOverflowError — ay isang nakamamatay na error ng Java Virtual Machine (JVM) o Android Runtime (ART), na nangyayari kapag ang call stack ng thread ay umabot sa maximum na pinapayagang lalim. Hindi tulad ng OutOfMemoryError (kakulangan ng Heap), ang StackOverflowError ay nauugnay sa ibang lugar ng memorya — ang stack, kung saan nakaimbak ang mga frame ng tawag ng method at lokal na variable.
Bawat tawag ng method ay lumilikha ng frame sa stack: return address, parameter at lokal na variable. Sa pagbabalik mula sa method, ang frame ay nawawasak. Kung ang method ay tumatawag sa sarili nito (recursion) nang walang base na kondisyon, ang mga frame ay nag-iipon hanggang mapuno ang stack. Hindi maaaring maglaan ng bagong frame ang JVM at nagtatapon ng StackOverflowError na may mensaheng „null” (sa Java) o may indikasyon ng walang katapusang umuulit na stack string.
Ang laki ng stack ng thread ay nakatakda sa paggawa at hindi nagbabago habang isinasagawa. Sa Android, ang tipikal na laki ng stack ng pangunahing thread ay 32–48 KB, na nagbibigay ng lalim na humigit-kumulang 512–1024 frame para sa mga method na walang maraming lokal na variable. Para sa mga background thread, ang default na laki ay mas maliit — 16–24 KB.
Ang call stack — ay isang istraktura ng datos na LIFO (Last In, First Out) na namamahala sa pagkakasunud-sunod ng pagpapatupad ng mga method. Sa bawat oras na tumawag ang programa ng method, lumilikha ang JVM ng frame sa stack at inilalagay ito sa itaas. Kapag natapos ang method, ang frame ay tinatanggal.
Bawat frame ay naglalaman ng: operand stack (stack ng operand para sa mga bytecode instruction), array of local variables (kasama ang this), reference to constant pool at return address. Kung mas maraming lokal na variable ang isang method, mas malaki ang laki ng frame nito at mas kaunting method ang maaaring tawagin bago mapuno ang stack. Ang isang method na may 10 parameter at 20 lokal na variable ay sumasakop ng halos 3 beses na mas maraming espasyo kaysa sa isang method na walang parameter.
Sa Android gumagamit ang ART ng sarili nitong implementasyon ng stack, na naiiba sa Desktop JVM. Maaaring palakihin ng ART ang stack nang dinamiko sa ilang mga limitasyon, ngunit para sa bawat thread ay mayroon pa ring matigas na limitasyon. Ang pangunahing thread (UI thread) ay may pinakamalaking stack, dahil ang buong lifecycle ng Activity at pagproseso ng mga event ay isinasagawa dito.
// Recursion na humahantong sa StackOverflowError
fun recursiveCall(depth: Int): Int {
return recursiveCall(depth + 1) // walang base na kondisyon
}
// Tawag ay hahantong sa StackOverflowError sa lalim na ~1000
recursiveCall(0)
Limang tipikal na senaryo ang humahantong sa StackOverflowError sa mga mobile application. Karamihan sa mga ito ay nauugnay sa recursion, ngunit mayroon ding hindi gaanong halatang mga sanhi.
Ang pinakakaraniwang sanhi. Sumusulat ang developer ng recursive method na walang kondisyon ng paghinto o may kondisyong hindi nagiging true. Bawat tawag ay nagdaragdag ng frame, at napupuno ang stack pagkatapos ng 500–2000 iteration depende sa laki ng frame. Tipikal na halimbawa: pagkalkula ng factorial n! nang walang pagsusuri ng n == 0.
Suriin ang base na kondisyon sa simula ng bawat recursive method. Sa Kotlin, gumamit ng require() o check() para sa pag-validate ng parameter sa simula. Para sa malalim na recursion (higit sa 100 level) isaalang-alang ang pagpapalit ng iterative approach.
Ang Class A ay lumilikha ng instance ng B, ang class B ay lumilikha ng instance ng A — ito ay isang siklikong dependency sa mga constructor. Kapag sinusubukang gumawa ng A, ang constructor ng B ay tinatawag, na tumatawag sa constructor ng A, at iba pa hanggang StackOverflowError. Nakikita ng mga DI framework (Dagger, Hilt) ang mga ganitong cycle sa yugto ng compilation, ngunit hindi sila nahuhuli ng manu-manong paggawa ng object.
Gumamit ng Dependency Injection na may mga dependency graph: sinusuri ng Dagger o Koin ang mga cycle sa yugto ng build. Kung hindi maiiwasan ang cycle, palitan ang direktang dependency ng interface na may lazy initialization o Provider factory.
// Siklikong dependency — StackOverflowError
class A(private val b: B)
class B(private val a: A)
// Lazy na solusyon
class A(private val bProvider: Provider<B>)
Ang pagtawid sa puno ng View (ViewGroup.getChildAt()), file system o JSON structure sa pamamagitan ng recursion ay maaaring lumampas sa limitasyon ng stack sa lalim na higit sa 500–1000 elemento. Ang Android ViewGroup na may nesting na 20 level ay bihira, ngunit ang recursive parsing ng JSON na may 2000 nested object ay isang makatotohanang senaryo.
Palitan ang recursive na pagtawid ng iterative sa pamamagitan ng tahasang Stack<T> o ArrayDeque. Ito ay ganap na nag-aalis ng panganib ng pag-apaw ng stack, dahil ang mga object sa heap ay hindi limitado ng limitasyon ng stack. Ang BFS (Breadth-First Search) sa pamamagitan ng Queue ay nilulutas din ang problema.
Sanhi na tiyak sa Android: siklikong tawag ng mga method ng lifecycle sa maling pagproseso ng configuration. Halimbawa, sa onConfigurationChanged, ang recreate() ay tinatawag, na muling tumatawag sa onConfigurationChanged, at iba pa hanggang StackOverflowError. Katulad: setContentView() sa loob ng onLayout(), na nagdudulot ng paulit-ulit na pagsukat at layout.
Huwag tawagin ang recreate() sa loob ng mga method na nauugnay sa pagbabago ng configuration. Para sa pag-update ng UI sa pagbabago ng tema, gumamit ng setTheme() nang walang recreate. Para sa dinamikong pagbabago ng oryentasyon — requestOrientation() nang isang beses, walang flag sa configuration.
Gson, Moshi o Kotlin Serialization kapag sinusubukang i-serialize ang isang object na may mga siklikong sanggunian (A ay tumutukoy sa B, B ay tumutukoy sa A) ay pumapasok sa walang hanggang recursion at bumabagsak na may StackOverflowError. Ito ay isang karaniwang problema sa pag-serialize ng Entity na may bidirectional Relationship (JPA, Room na may ForeignKey).
Gumamit ng @Transient, @JsonIgnore o @kotlinx.serialization.Transient para sa isa sa mga panig ng cycle. Para sa Gson — JsonSerializer na may tahasang paglilimita ng lalim. Para sa Room — huwag kailanman direktang i-serialize ang Entity, gumamit ng DTO mapper.
Ang diagnosis ng StackOverflowError ay mas simple kaysa sa ibang mga error sa memorya: ang stack trace sa karamihan ng mga kaso ay nagpapakita ng umuulit na pagkakasunod-sunod ng mga tawag. Ito ay agad na nagpapahiwatig ng recursion.
Ang stack trace ng StackOverflowError ay natatangi: pagkatapos ng unang 200–500 linya, nagsisimula ang pag-uulit ng parehong pattern ng tawag. Pinuputol ng JVM ang mga umuulit na linya sa dulo at nagpapakita ng „... 1234 more”. Ang bilang ng hindi umuulit na linya bago ang „...” ay nagpapakita ng lalim ng recursion na humantong sa error.
Basahin ang mga unang linya ng stack trace — ipinapakita nila kung saang method nagsimula ang pag-uulit. Hanapin ang method na tumatawag sa sarili nito o lumilikha ng chain ng tawag na bumabalik dito. Ayusin ang base na kondisyon o palitan ang recursion ng loop.
Pansamantala ang problema ay maaaring malutas sa pamamagitan ng pagpapalaki ng laki ng stack sa pamamagitan ng JVM flag -Xss. Sa Android, ang laki ng stack ay itinatakda sa pamamagitan ng AndroidManifest: ang android:largeHeap ay hindi nakakaapekto sa stack. Para palakihin ang stack ng thread sa code: Thread(ThreadGroup, Runnable, name, stackSize). stackSize — ang nais na laki sa bytes.
// Paggawa ng thread na may pinalaking stack
val thread = Thread(null, runnable, "big-stack-thread", 64 * 1024)
thread.start()
Mahalaga: ang pagpapalaki ng stack ay hindi nilulutas ang problema, pinapaantala lamang ito. Sa recursion ng 10,000 level, ang stack na 64 KB ay papalitan ng stack na 128 KB, na magbibigay ng 20,000 level — ngunit ang error ay magaganap pa rin, sa bandang huli lamang. Ang tanging tamang solusyon — iteratibong pagpapalit ng recursion.
Ang mga iteratibong algorithm ay hindi gumagamit ng call stack para mag-imbak ng mga intermediate na estado — iniimbak nila ang mga ito sa heap (Stack<T> o ArrayDeque). Ang pagtawid sa binary tree, pagkalkula ng factorial, Fibonacci — anumang recursion ay maaaring gawing iteration sa pamamagitan ng tahasang stack.
// Iteratibong pagtawid sa puno — walang panganib ng StackOverflow
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) }
}
}
Ang pag-iwas sa StackOverflowError ay isang hanay ng mga patakaran at tool na nakakita ng mga potensyal na recursive cycle bago sila pumasok sa production.
Magdagdag ng proteksiyong bilang ng lalim sa mga recursive method sa debug build. Kung ang lalim ay lumampas sa threshold (hal. 1000), magtapon ng exception na may malinaw na mensahe. Ito ay ginagawang isang naiintindihang business exception ang StackOverflowError na may hindi nababasang trace.
fun safeRecursive(n: Int, depth: Int = 0): Int {
if (depth > 1000) {
throw IllegalStateException("Recursion ay lumampas sa 1000 level")
}
return if (n <= 1) n
else safeRecursive(n - 1, depth + 1)
}
Detekt (Kotlin) at Infer (Facebook) ay nakakahanap ng potensyal na walang hanggang recursion sa antas ng static analysis. Ang Detekt ay may panuntunang PotentiallyInfiniteRecursion na nagbababala tungkol sa self-call nang walang pagbabago ng parameter. Isama ito sa set ng panuntunan ng CI at itakda ang severity sa error.
Sa code review bigyang-pansin ang: anumang self-call method, recursive na tawag sa loob ng lambda (inline function ng Kotlin), mga siklikong tawag sa pagitan ng iba't ibang klase, recursion sa property delegates. Para sa bawat recursive method suriin: may base na kondisyon ba, nagbabago ba ang parameter sa bawat hakbang, ginagarantiya ba ng pagbabago ng parameter ang pag-abot sa base na kondisyon.
Ang Kotlin ay sumusuporta sa tailrec modifier: kung ang isang recursive method ay minarkahan ng tailrec at ang tawag ay tail call (huling operasyon), ang compiler ay ginagawa itong iteration. Gayunpaman, ang tailrec ay gumagana lamang para sa self-call (direktang tinatawag ng method ang sarili nito), hindi gumagana para sa mutual recursion at hindi sinusuportahan sa Android-compatible na bersyon ng Kotlin bago ang 1.5.
tailrec fun factorial(n: Int, acc: Int = 1): Int {
return if (n <= 1) acc
else factorial(n - 1, acc * n) // tail call
}
Mga madalas itanong
Maaari, ngunit sa antas lamang ng Java. Ang Error, tulad ng Exception, ay Throwable. Gayunpaman, pagkatapos ng StackOverflowError ang stack ay nasira — ang mga frame na hindi kasya ay hindi maaaring matapos nang tama. Ang pagtatangkang lumikha ng bagong object sa catch block ay maaaring magdulot ng isa pang StackOverflowError.
Para sa pangunahing thread — 32–48 KB, para sa background thread — 16–24 KB. Ang eksaktong laki ay depende sa bersyon ng Android at manufacturer ng device. Gumagamit ang ART ng dinamikong pagpapalawak ng stack, ngunit hindi hihigit sa 2× mula sa paunang halaga.
Sa Kotlin — oo, kung ang method ay minarkahan ng tailrec. Ginagawa ng compiler ang tail recursion sa iteration, ganap na inaalis ang paglaki ng stack. Sa Java, ang tail recursion ay hindi na-o-optimize ng JVM (hindi tulad ng mga functional na wika tulad ng Scala).
Ang laki ng stack sa emulator at tunay na device ay maaaring magkaiba. Gumagamit ang emulator ng Desktop JVM na may tipikal na stack na 512–1024 KB, habang ang Android ART ay 32–48 KB. Ang error ay lilitaw sa ART nang mas maaga kaysa sa Desktop JVM.
Lugar ng memorya: StackOverflowError — error ng stack (mga frame ng tawag), OutOfMemoryError — error ng heap (mga object). Ang StackOverflowError ay halos palaging sanhi ng recursion, habang ang OutOfMemoryError ay sanhi ng pagtagas ng memorya o malalaking object.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.