Mobil geliştirmede Stack Overflow — nedir, nedenleri ve önleme yöntemleri

Yazar: IT Sectr Yayınlanma: 2026-03-29 Okuma süresi: 9 dk

Stack Overflow, bir iş parçacığının maksimum yığın derinliği aşıldığında ortaya çıkan bir çağrı yığını taşması hatasıdır (java.lang.StackOverflowError). Java Virtual Machine Specification'a göre, 64-bit sistemler için JVM'de tipik yığın derinliği 1024 çerçevedir. Ana neden, temel koşulu olmayan sonsuz özyinelemedir.

Önemli Noktalar

  • StackOverflowError — çağrı yığını derinlik sınırı aşıldığında JVM hatası
  • Yığın derinliği yapılandırmaya bağlı olarak 512–2048 çerçeve ile sınırlıdır
  • Sonsuz özyineleme StackOverflowError'un en yaygın nedenidir
  • Kuyruk özyinelemesi işlevsel dillerin aksine JVM'de optimize edilmez
  • Yinelemeli değiştirme taşmayı önlemenin güvenilir bir yoludur

Stack Overflow Nedir

StackOverflowError, bir iş parçacığının çağrı yığını izin verilen maksimum derinliğe ulaştığında ortaya çıkan Java Sanal Makinesi (JVM) veya Android Runtime (ART) ölümcül hatasıdır. OutOfMemoryError'dan (Heap tükenmesi) farklı olarak StackOverflowError, belleğin farklı bir alanıyla — yöntem çağrı çerçevelerinin ve yerel değişkenlerin depolandığı yığınla ilgilidir.

Her yöntem çağrısı yığında bir çerçeve oluşturur: dönüş adresi, parametreler ve yerel değişkenler. Yöntemden dönüşte çerçeve yok edilir. Bir yöntem temel koşul olmadan kendini çağırırsa (özyineleme), yığın dolana kadar çerçeveler birikir. JVM yeni bir çerçeve ayıramaz ve «null» mesajıyla (Java'da) veya sonsuzca tekrar eden bir yığın satırı göstergesiyle StackOverflowError fırlatır.

Bir iş parçacığının yığın boyutu oluşturulurken sabitlenir ve yürütme sırasında değişmez. Android'de, ana iş parçacığının tipik yığın boyutu 32–48 KB'dir ve bu, çok fazla yerel değişkeni olmayan yöntemler için yaklaşık 512–1024 çerçeve derinliği sağlar. Arka plan iş parçacıkları için varsayılan boyut daha küçüktür — 16–24 KB.

Çağrı Yığını Nasıl Çalışır

Çağrı yığını (Call Stack), yöntem yürütme sırasını yöneten bir LIFO (Last In, First Out) veri yapısıdır. Program bir yöntemi her çağırdığında, JVM yığında bir çerçeve oluşturur ve onu en üste yerleştirir. Yöntem tamamlandığında çerçeve çıkarılır.

Her çerçeve şunları içerir: işlenen yığını (bayt kodu talimatları için), yerel değişkenler dizisi (this dahil), sabit havuzuna referans ve dönüş adresi. Bir yöntemin ne kadar çok yerel değişkeni varsa, çerçeve boyutu o kadar büyük olur ve yığın dolmadan önce o kadar az yöntem çağrılabilir. 10 parametreli ve 20 yerel değişkenli bir yöntem, parametresiz bir yöntemden yaklaşık 3 kat daha fazla yer kaplar.

Android'de, ART masaüstü JVM'den farklı olarak kendi yığın uygulamasını kullanır. ART, belirli sınırlar içinde yığını dinamik olarak artırabilir, ancak her iş parçacığı için hala sabit bir sınır vardır. Ana iş parçacığı (UI iş parçacığı), tüm Activity yaşam döngüsünü ve olay işlemeyi yönettiği için en büyük yığına sahiptir.

kotlin
// StackOverflowError'a yol açan özyineleme
fun recursiveCall(depth: Int): Int {
    return recursiveCall(depth + 1) // temel koşul yok
}

// Çağrı ~1000 derinliğinde StackOverflowError'a neden olur
recursiveCall(0)

Yığın Taşmasının Ana Nedenleri

Beş tipik senaryo mobil uygulamalarda StackOverflowError'a yol açar. Çoğu özyineleme ile ilgilidir, ancak daha az belirgin nedenler de vardır.

Temel Koşulsuz Sonsuz Özyineleme

En yaygın neden. Geliştirici, durma koşulu olmayan veya asla true olmayan bir koşula sahip özyinelemeli bir yöntem yazar. Her çağrı bir çerçeve ekler ve yığın, çerçeve boyutuna bağlı olarak 500–2000 yinelemede dolar. Tipik bir örnek: n == 0'ı kontrol etmeden n! faktöriyelini hesaplamak.

Her özyinelemeli yöntemin başında temel koşulu kontrol edin. Kotlin'de, parametre doğrulaması için require() veya check() kullanın. Derin özyineleme (100 seviyeden fazla) için yinelemeli bir yaklaşımla değiştirmeyi düşünün.

Yapıcılarda Döngüsel Bağımlılıklar

A Sınıfı B'nin bir örneğini oluşturur, B sınıfı A'nın bir örneğini oluşturur — bu, yapıcılarda döngüsel bir bağımlılıktır. A'yı oluşturmaya çalışırken, B'nin yapıcısı çağrılır, bu da A'nın yapıcısını çağırır ve StackOverflowError'a kadar böyle devam eder. DI çerçeveleri (Dagger, Hilt) bu tür döngüleri derleme zamanında tespit eder, ancak manuel nesne oluşturma bunları yakalamaz.

Bağımlılık grafikleriyle Bağımlılık Enjeksiyonu kullanın: Dagger veya Koin, derleme zamanında döngüleri kontrol eder. Döngü kaçınılmazsa, doğrudan bağımlılığı tembel başlatma veya Provider fabrikası olan bir arayüzle değiştirin.

kotlin
// Döngüsel bağımlılık — StackOverflowError
class A(private val b: B)
class B(private val a: A)

// Tembel çözüm
class A(private val bProvider: Provider<B>)

Graf Geçişinde Derin Özyineleme

Özyineleme yoluyla bir View ağacı (ViewGroup.getChildAt()), dosya sistemi veya JSON yapısının geçişi, 500–1000 öğeden daha fazla derinlikte yığın sınırını aşabilir. 20 seviye iç içe geçmiş bir Android ViewGroup nadirdir, ancak 2000 iç içe nesneyle özyinelemeli JSON ayrıştırma gerçek bir senaryodur.

Özyinelemeli geçişi, açık bir Stack<T> veya ArrayDeque kullanarak yinelemeli geçişle değiştirin. Bu, yığın taşması riskini tamamen ortadan kaldırır çünkü yığın nesneleri yığın sınırıyla sınırlı değildir. Queue aracılığıyla BFS (Breadth-First Search) de sorunu çözer.

onConfigurationChanged'in Yanlış İşlenmesi

Android'e özgü bir neden: yapılandırma yanlış işlendiğinde yaşam döngüsü yöntemlerinin döngüsel çağrısı. Örneğin, onConfigurationChanged içinde recreate() çağırmak, tekrar onConfigurationChanged'i çağırır ve StackOverflowError'a kadar böyle devam eder. Benzer şekilde: onLayout() içinde setContentView(), başka bir ölçüm ve düzeni tetikler.

Yapılandırma değişiklikleriyle ilgili yöntemlerin içinde recreate() çağırmayın. Tema değişikliğinde UI'yi güncellemek için recreate olmadan setTheme() kullanın. Dinamik yön değişiklikleri için — yapılandırmada bir işaret olmadan requestOrientation()'ı bir kez çağırın.

Döngüsel Referanslarla Serileştirme

Gson, Moshi veya Kotlin Serialization, döngüsel referanslara (A, B'yi referans alır, B, A'yı referans alır) sahip bir nesneyi serileştirmeye çalışırken sonsuz özyinelemeye girer ve StackOverflowError ile çöker. Bu, çift yönlü ilişkilere (JPA, ForeignKey ile Room) sahip Entity'leri serileştirirken yaygın bir sorundur.

Döngünün bir tarafında @Transient, @JsonIgnore veya @kotlinx.serialization.Transient kullanın. Gson için — açık derinlik sınırı olan JsonSerializer. Room için — Entity'yi asla doğrudan serileştirmeyin, DTO eşleyicileri kullanın.

StackOverflowError Nasıl Teşhis Edilir ve Düzeltilir

StackOverflowError teşhisi diğer bellek hatalarından daha kolaydır: yığın izi çoğu durumda tekrar eden bir çağrı dizisi gösterir. Bu hemen özyinelemeyi işaret eder.

Yığın İzini Okuma

StackOverflowError'un yığın izi benzersizdir: ilk 200–500 satırdan sonra aynı çağrı deseni tekrarlanmaya başlar. JVM, sonunda tekrar eden satırları keser ve «... 1234 more» gösterir. «...» öncesindeki tekrarlanmayan satırların sayısı, hataya neden olan özyineleme derinliğini gösterir.

Yığın izinin ilk satırlarını okuyun — tekrarın hangi yöntemle başladığını gösterirler. Kendini çağıran veya kendisine dönen bir çağrı zinciri oluşturan yöntemi bulun. Temel koşulu düzeltin veya özyinelemeyi bir döngüyle değiştirin.

Yığın Boyutunu Artırma (Geçici Çözüm)

Geçici olarak, sorun JVM bayrağı -Xss ile yığın boyutunu artırarak çözülebilir. Android için yığın boyutu AndroidManifest aracılığıyla ayarlanır: android:largeHeap yığını etkilemez. Kodda iş parçacığı yığınını artırmak için: Thread(ThreadGroup, Runnable, name, stackSize). stackSize, bayt cinsinden istenen boyuttur.

kotlin
// Artırılmış yığınla iş parçacığı oluşturma
val thread = Thread(null, runnable, "big-stack-thread", 64 * 1024)
thread.start()

Önemli: yığını artırmak sorunu çözmez, yalnızca geciktirir. 10.000 seviyeli özyinelemede, 64 KB'lik bir yığın 128 KB'lik bir yığınla değiştirilir ve 20.000 seviye verir — ancak hata yine de oluşur, sadece daha sonra. Tek doğru çözüm, özyinelemenin yinelemeli olarak değiştirilmesidir.

Özyinelemeyi Yinelemeyle Değiştirme

Yinelemeli algoritmalar, ara durumları depolamak için çağrı yığınını kullanmaz — onları yığında (Stack<T> veya ArrayDeque) depolar. İkili ağaç geçişi, faktöriyel hesaplama, Fibonacci — herhangi bir özyineleme, açık bir yığın kullanılarak yinelemeye dönüştürülebilir.

kotlin
// Yinelemeli ağaç geçişi — StackOverflow riski yok
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) }
    }
}

Stack Overflow Nasıl Önlenir

StackOverflowError'u önleme, potansiyel özyinelemeli döngüleri üretime ulaşmadan önce tanımlayan bir dizi kural ve araçtır.

Hata Ayıklama Yapısında Özyineleme Derinlik Sınırı

Hata ayıklama yapılarında özyinelemeli yöntemlere koruyucu bir derinlik sayacı ekleyin. Derinlik bir eşiği (örneğin, 1000) aşarsa, net bir mesajla bir istisna fırlatın. Bu, okunamaz bir iz ile StackOverflowError'u anlaşılabilir bir iş istisnasına dönüştürür.

kotlin
fun safeRecursive(n: Int, depth: Int = 0): Int {
    if (depth > 1000) {
        throw IllegalStateException("Özyineleme 1000 seviyeyi aştı")
    }
    return if (n <= 1) n
           else safeRecursive(n - 1, depth + 1)
}

Statik Kod Analizi

Detekt (Kotlin) ve Infer (Facebook), statik analiz seviyesinde potansiyel sonsuz özyinelemeleri bulur. Detekt, parametreleri değiştirmeden kendi kendine çağrı hakkında uyaran PotentiallyInfiniteRecursion kuralına sahiptir. CI kural setinde etkinleştirin ve ciddiyeti error olarak ayarlayın.

Özyinelemeye Odaklanan Kod İncelemesi

Kod incelemesinde şunlara dikkat edin: kendi kendine çağrı yapan yöntemler, lambda içindeki özyinelemeli çağrılar (Kotlin satır içi işlevleri), farklı sınıflar arasındaki döngüsel çağrılar, özellik temsilcilerinde özyineleme. Her özyinelemeli yöntem için kontrol edin: temel koşul var mı, parametre her adımda değişiyor mu, parametre değişikliği temel koşula ulaşmayı garanti ediyor mu.

Kuyruk-Özyinelemeli Dönüşüm (Sınırlı)

Kotlin tailrec değiştiricisini destekler: özyinelemeli bir yöntem tailrec ile işaretlenmişse ve çağrı kuyruk (son işlem) ise, derleyici onu yinelemeye dönüştürür. Ancak tailrec yalnızca kendi kendine çağrı (yöntem doğrudan kendini çağırır) için çalışır, karşılıklı özyineleme için çalışmaz ve Android uyumlu Kotlin sürüm 1.5'ten önce desteklenmez.

kotlin
tailrec fun factorial(n: Int, acc: Int = 1): Int {
    return if (n <= 1) acc
           else factorial(n - 1, acc * n) // kuyruk çağrısı
}

Sıkça Sorulan Sorular

StackOverflowError try-catch ile yakalanabilir mi?

Evet, ancak yalnızca Java seviyesinde. Error, Exception gibi, bir Throwable'dır. Ancak StackOverflowError'dan sonra yığın hasar görür — sığmayan çerçeveler doğru şekilde tamamlanamaz. catch bloğunda yeni bir nesne oluşturma girişimi başka bir StackOverflowError'a neden olabilir.

Android'de varsayılan yığın boyutu nedir?

Ana iş parçacığı için — 32–48 KB, arka plan iş parçacıkları için — 16–24 KB. Kesin boyut Android sürümüne ve cihaz üreticisine bağlıdır. ART dinamik yığın genişletme kullanır ancak başlangıç değerinin 2 katından fazla olmaz.

Kuyruk özyinelemesi StackOverflowError'u önleyebilir mi?

Kotlin'de — evet, yöntem tailrec ile işaretlenmişse. Derleyici kuyruk özyinelemesini yinelemeye dönüştürür ve yığın büyümesini tamamen ortadan kaldırır. Java'da, kuyruk özyinelemesi JVM tarafından optimize edilmez (Scala gibi işlevsel dillerin aksine).

StackOverflowError neden öykünücüde oluyor ama cihazda olmuyor?

Yığın boyutu öykünücü ve gerçek cihazda farklılık gösterebilir. Öykünücü, tipik 512–1024 KB yığınlı masaüstü JVM kullanırken, Android ART 32–48 KB kullanır. Hata, masaüstü JVM'den önce ART'de ortaya çıkacaktır.

StackOverflowError, OutOfMemoryError'dan nasıl farklıdır?

Bellek alanı: StackOverflowError yığın hatası (çağrı çerçeveleri), OutOfMemoryError yığın (heap) hatasıdır (nesneler). StackOverflowError neredeyse her zaman özyinelemeden kaynaklanırken, OutOfMemoryError bellek sızıntılarından veya büyük nesnelerden kaynaklanır.

Özet

  • StackOverflowError — özyineleme derinlik sınırı aşıldığında çağrı yığını taşması
  • Android'de yığın derinliği ana iş parçacığında 512–1024 çerçevedir
  • Sonsuz özyineleme ana nedendir; her özyinelemeli yöntemde temel koşulu kontrol edin
  • Yapıcılarda döngüsel bağımlılıklar — daha az belirgin ancak yaygın bir taşma nedeni
  • Açık Stack<T> ile özyinelemenin yinelemeli değiştirilmesi riski tamamen ortadan kaldırır
  • Kotlin'de tailrec kuyruk özyinelemesini derleyici seviyesinde yinelemeye dönüştürür
  • Statik analiz (Detekt, Infer) çalışma zamanından önce potansiyel sonsuz özyinelemeleri bulur

Anahtar teslim bir mobil uygulama geliştireceğiz

IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.

Projeyi tartış

Ayrıca okuyun