StackOverflowError nedir?


439

Nedir, StackOverflowErrorneden olur ve bunlarla nasıl başa çıkmalıyım?


Java'daki yığın boyutu küçük. Ve birçok kez tekrarlanan çağrı gibi bu problemle karşı karşıyasınız. Kodunuzu döngüye göre yeniden tasarlayabilirsiniz. Bunu yapmak için genel tasarım kalıbını bu url'de bulabilirsiniz: jndanial.com/73
JNDanial

Bunu almanın açık olmayan bir yolu: satırı new Object() {{getClass().newInstance();}};bazı statik bağlamlara ekleyin (örneğin mainyöntem). Örnek bağlamından çalışmaz (yalnızca atar InstantiationException).
John McClane

Yanıtlar:


408

Parametreler ve yerel değişkenler yığına tahsis edilir (referans türleriyle, nesne öbek üzerinde yaşar ve yığında bu nesneyi referanstaki bir değişken). Yığın genellikle adres alanınızın üst ucunda yaşar ve kullanıldıkça adres alanının altına doğru (yani sıfıra doğru) gider .

İşleminizde ayrıca işleminizin alt ucunda yaşayan bir yığın vardır . Belleği ayırdıkça, bu yığın adres alanınızın üst ucuna doğru büyüyebilir. Gördüğünüz gibi, yığının yığınla "çarpışması" olasılığı vardır (biraz tektonik plakalar gibi !!!).

Yığın taşmasının en yaygın nedeni hatalı bir yinelemeli çağrıdır . Genellikle, özyinelemeli işlevleriniz doğru sonlandırma durumuna sahip olmadığında, bu nedenle kendini sonsuza dek çağırır. Veya fesih koşulu iyi olduğunda, bunun yerine getirilmeden önce çok fazla yinelemeli çağrı yapılması gerekebilir.

Bununla birlikte, GUI programlama ile dolaylı özyineleme oluşturmak mümkündür . Örneğin, uygulamanız boya mesajlarını işliyor olabilir ve bunları işlerken, sistemin başka bir boya mesajı göndermesine neden olan bir işlevi çağırabilir. Burada açıkça kendinizi aramadınız, ancak OS / VM sizin için yaptı.

Onlarla başa çıkmak için kodunuzu incelemeniz gerekir. Kendilerini çağıran işlevleriniz varsa, sonlandırma koşulunuz olup olmadığını kontrol edin. Varsa, işlevi çağırırken bağımsız değişkenlerden en az birini değiştirdiğinizi, aksi takdirde özyinelemeli olarak adlandırılan işlev için görünür bir değişiklik olmayacağını ve sonlandırma koşulunun yararsız olduğunu kontrol edin. Ayrıca, geçerli bir sonlandırma durumuna ulaşmadan önce yığın alanınızın belleğinin bitebileceğini unutmayın, bu nedenle yönteminizin daha fazla yinelemeli çağrı gerektiren giriş değerlerini işleyebildiğinden emin olun.

Açık bir özyinelemeli işleviniz yoksa, dolaylı olarak işlevinizin çağrılmasına neden olacak herhangi bir kütüphane işlevini çağırıp çağırmadığınızı kontrol edin (yukarıdaki örtük durum gibi).


1
Orijinal poster: hey bu harika. Yığın taşmalarından özyineleme her zaman sorumlu mudur? Yoksa onlardan başka şeyler de sorumlu olabilir mi? Maalesef bir kütüphane kullanıyorum ... ama anladığım bir kütüphane değil.
Ziggy

4
Ha ha ha, işte burada: while (puanlar <100) {addMouseListeners (); ) (moveball; checkforcollision (); pause (speed);} Vay canına, bir sürü fare dinleyicisi ile sonuçlanacağımı fark etmediğim için topal hissediyorum ... Teşekkürler çocuklar!
Ziggy

4
Hayır, en.wikipedia.org/wiki/Stack_overflow adresindeki Wikipedia makalesine bakarsanız, yığın taşmaları yığın üzerinde tahsis edilemeyecek kadar büyük değişkenlerden de gelebilir .
JB King

8
Bir yığın taşma hatasını "işlemenin" neredeyse imkansız olduğu belirtilmelidir . Çoğu ortamda, hatayı işlemek için yığın üzerinde kod çalıştırılması gerekir, bu da artık yığın alanı yoksa zordur.
Hot Licks

3
@JB King: Yığın üzerinde yalnızca ilkel türlerin ve referansların tutulduğu Java için geçerli değildir. Tüm büyük şeyler (diziler ve nesneler) öbek üzerinde.
jcsahnwaldt diyor GoFundMonica

107

Bunu tanımlamak için önce yerel değişkenlerin ve nesnelerin nasıl saklandığını anlayalım.

Yerel değişken yığın halinde saklanır : resim açıklamasını buraya girin

Resme bakarsanız, işlerin nasıl çalıştığını anlayabilmelisiniz.

Bir Java uygulaması tarafından bir işlev çağrısı çağrıldığında, çağrı yığınına bir yığın çerçevesi atanır. Yığın çerçevesi, çağrılan yöntemin parametrelerini, yerel parametrelerini ve yöntemin dönüş adresini içerir. Dönüş adresi, çağrılan yöntem döndürüldükten sonra program yürütmesinin devam edeceği yürütme noktasını belirtir. Yeni bir yığın çerçevesi için yer yoksa, StackOverflowErrorJava Sanal Makinesi (JVM) tarafından atılır.

Bir Java uygulamasının yığınını tüketebilecek en yaygın durum özyineleme'dir. Özyinelemede, bir yöntem yürütülmesi sırasında kendini çağırır. Özyineleme, güçlü bir genel amaçlı programlama tekniği olarak kabul edilir, ancak önlemek için dikkatli kullanılmalıdır StackOverflowError.

A atma örneği StackOverflowErroraşağıda gösterilmiştir:

StackOverflowErrorExample.java:

public class StackOverflowErrorExample {

  public static void recursivePrint(int num) {
    System.out.println("Number: " + num);

    if (num == 0)
      return;
    else
      recursivePrint(++num);
  }

  public static void main(String[] args) {
    StackOverflowErrorExample.recursivePrint(1);
  }
}

Bu örnekte, recursivePrintbir tamsayı yazdırıp sonra bir sonraki ardışık tamsayı argüman olarak çağıran özyinelemeli bir yöntem tanımlarız . Yineleme 0, parametre olarak geçene kadar sona erer . Bununla birlikte, örneğimizde, parametreyi 1'den ve onun takip eden takipçilerinden geçtik, sonuç olarak, özyineleme asla sona ermeyecektir.

-Xss1M1MB'ye eşit iş parçacığı yığınının boyutunu belirten bayrağı kullanan bir örnek yürütme aşağıda gösterilmiştir:

Number: 1
Number: 2
Number: 3
...
Number: 6262
Number: 6263
Number: 6264
Number: 6265
Number: 6266
Exception in thread "main" java.lang.StackOverflowError
        at java.io.PrintStream.write(PrintStream.java:480)
        at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
        at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
        at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
        at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
        at java.io.PrintStream.write(PrintStream.java:527)
        at java.io.PrintStream.print(PrintStream.java:669)
        at java.io.PrintStream.println(PrintStream.java:806)
        at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:4)
        at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
        at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
        at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
        ...

JVM'nin ilk yapılandırmasına bağlı olarak sonuçlar farklılık gösterebilir, ancak sonunda StackOverflowErroratılır. Bu örnek, dikkatli bir şekilde uygulanmazsa, özyinelemenin nasıl sorunlara neden olabileceğinin çok iyi bir örneğidir.

StackOverflowError ile nasıl başa çıkılır

  1. En basit çözüm, yığın izlemesini dikkatle incelemek ve satır numaralarının tekrar eden desenini tespit etmektir. Bu satır numaraları, özyinelemeli olarak adlandırılan kodu gösterir. Bu satırları algıladıktan sonra, kodunuzu dikkatlice incelemeli ve özyinelemenin neden hiç sona ermediğini anlamalısınız.

  2. Özyinelemenin doğru bir şekilde uygulandığını doğruladıysanız, daha fazla sayıda çağrmaya izin vermek için yığının boyutunu artırabilirsiniz. Yüklenen Java Sanal Makinesi'ne (JVM) bağlı olarak, varsayılan iş parçacığı yığın boyutu 512 KB veya 1 MB'ye eşit olabilir . -XssBayrağı kullanarak iplik yığını boyutunu artırabilirsiniz . Bu bayrak, projenin yapılandırması veya komut satırı aracılığıyla belirtilebilir. -XssArgümanın biçimi : -Xss<size>[g|G|m|M|k|K]


Bazı java sürümlerinde -Xss argümanının sadece yeni iş parçacıkları üzerinde etkili olduğu pencereleri kullanırken bir hata var gibi görünüyor
goerlibe

65

Gibi bir fonksiyonunuz varsa:

int foo()
{
    // more stuff
    foo();
}

Daha sonra foo () kendini çağırmaya, derinleşmeye ve derinleşmeye devam eder ve içinde bulunduğunuz işlevleri doldurmak için kullanılan alan dolduğunda yığın taşması hatası alırsınız.


12
Yanlış. İşleviniz kuyruk yinelemelidir. Derlenmiş dillerin çoğunda kuyruk özyineleme optimizasyonu bulunur. Bu, özyinelemenin basit bir döngüye dönüştüğü anlamına gelir ve bazı sistemlerde bu kod parçasıyla yığın taşmasına asla vurmazsınız.
Neşeli

Neşeli, hangi işlevsel olmayan diller kuyruk yinelemesini destekliyor?
horseyguy

@banister ve bazı javascript uygulamaları
Pacerier

@horseyguy Scala, Kuyruk özyineleme desteğine sahiptir.
Ajit K'sagar

Bu, bir yığın taşması yaratabilecek şeyin özünü yakalar. Güzel.
Pixel

24

Yığın taşması tam olarak şu anlama gelir: yığın taşması. Genellikle programda, yerel kapsam değişkenleri ve rutin yürütme sona erdiğinde nereye dönüleceği adreslerini içeren bir yığın bulunur. Bu yığın, bellekte bir yerde sabit bir bellek aralığı olma eğilimindedir, bu nedenle ne kadar değer içerebileceği sınırlıdır.

Yığın boşsa, patlayamazsınız, eğer yaparsanız yığın düşüklüğü hatası alırsınız.

Yığın doluysa, itemezsiniz, eğer yaparsanız yığın taşması hatası alırsınız.

Böylece yığına çok fazla tahsis ettiğiniz yerde yığın taşması görünür. Örneğin, söz konusu özyinelemede.

Bazı uygulamalar bazı özyineleme biçimlerini optimize eder. Özellikle kuyruk özyineleme. Kuyruk özyinelemeli rutinler, özyinelemeli çağrının rutinin yaptığı son şey olarak göründüğü rutinler biçimidir. Bu rutin çağrı basitçe bir sıçramaya dönüşür.

Bazı uygulamalar özyineleme için kendi yığınlarını uygulayana kadar devam eder, bu nedenle özyineleme, sistemin belleği bitene kadar devam etmesine izin verir.

Deneyebileceğiniz en kolay şey, yapabileceğiniz yığın boyutunuzu artırmak olacaktır. Bunu yapamazsanız, ikinci en iyi şey, yığın taşmasına neden olan bir şey olup olmadığına bakmak olacaktır. Çağrıdan önce ve sonra bir şeyi rutin olarak yazdırarak deneyin. Bu, başarısız rutini bulmanıza yardımcı olur.


4
Yığın taşması diye bir şey var mı ?
Pacerier

5
Derlemede bir yığın alt akışı mümkündür (ittiğinizden daha fazla haşhaş), derlenmiş dillerde neredeyse imkansız olurdu. Emin değilim, negatif boyutları "destekleyen" C'nin alloca () bir uygulama bulabilirsiniz.
0'da skor

2
Yığın taşması tam olarak şu anlama gelir: yığın taşması. Genellikle programda yerel kapsam değişkenleri içeren bir yığın vardır -> Hayır, her iş parçacığının yerel değişkenleri içeren her yöntem çağrısı için yığın kareleri içeren kendi yığını vardır ..
Koray Tugay

9

Yığın taşması genellikle yuva çağrıları işlevinin çok derinlemesine (özyineleme kullanıldığında kolay, yani kendisini çağıran bir işlev) veya yığının kullanılmasının daha uygun olacağı yığın üzerinde büyük miktarda bellek ayrılmasıyla çağrılır.


1
Hata! Java etiketini görmedim
Greg

Ayrıca, buradaki orijinal posterden: iç içe geçme işlevleri çok derinlerde ne? Diğer fonksiyonlar? Ve: Bir kişi yığına veya yığına nasıl bellek ayırır (çünkü biliyorsunuz, bu şeylerden birini bilmeden açıkça yaptım).
Ziggy

@Ziggy: Evet, bir işlev başka bir işlevi çağırırsa, başka bir işlevi çağırır ve bu şekilde devam ederse, birçok düzeyden sonra programınızda yığın taşması olur. [devam ediyor]
Chris Jester-Young

[... devamı] Java'da hafızayı doğrudan yığından ayıramazsınız (oysa C'de bunu yapabilirsiniz ve bu da izlenmesi gereken bir şey olacaktır), bu nedenle bu olası değildir. Java'da, tüm doğrudan ayırmalar öbekten "yeni" kullanarak gelir.
Chris Jester-Young

@ ChrisJester-Young ben bir yöntemde 100 yerel değişkenler, varsa doğru değil mi hepsi bunun istisnasız yığını oluyor?
Pacerier

7

Dediğiniz gibi, bir kod göstermeniz gerekiyor. :-)

Yığın taşması hatası genellikle işleviniz çok derin yuvalama çağırdığında oluşur. Bunun nasıl gerçekleştiğine ilişkin bazı örnekler için Stack Overflow Code Golf iş parçacığına bakın (bu soru olması durumunda, yanıtlar bilerek yığın taşmasına neden olur).


1
Tamamen kod eklemek istiyorum, ama ne yığın taşmalarına neden bilmiyorum gibi hangi kod eklemek emin değilim. tüm kodu eklemek topal olurdu, değil mi?
Ziggy

Projeniz açık kaynaklı mı? Öyleyse, bir Sourceforge veya github hesabı oluşturun ve tüm kodunuzu oraya yükleyin. :-)
Chris Jester-Young

bu harika bir fikir gibi geliyor, ama o kadar çok ben biriyim ki, ne yüklemem gerektiğini bile bilmiyorum. Gibi, vb genişletiyor olduğum sınıfları ithal kütüphane ... bana bilinmeyen vardır. Oh adamım: Kötü zamanlar.
Ziggy


5

StackOverflowErroryığına olduğu gibi OutOfMemoryErroryığına.

Sınırsız özyinelemeli çağrılar yığın alanının tükenmesine neden olur.

Aşağıdaki örnek aşağıdakileri üretir StackOverflowError:

class  StackOverflowDemo
{
    public static void unboundedRecursiveCall() {
     unboundedRecursiveCall();
    }

    public static void main(String[] args) 
    {
        unboundedRecursiveCall();
    }
}

StackOverflowError toplam olmayan bellek içi çağrıların (bayt cinsinden) toplam yığının boyutunu (bayt cinsinden) aşmasını önlemek için özyinelemeli çağrılar sınırlandırılmışsa önlenebilir.


3

Burada, tek başına bağlı bir listeyi tersine çevirmek için özyinelemeli bir algoritma örneği verilmiştir. Aşağıdaki özelliklere sahip bir dizüstü bilgisayarda (4G bellek, Intel Core i5 2.3GHz CPU, 64 bit Windows 7), bu işlev 10.000'e yakın bağlantılı bir liste için StackOverflow hatasıyla çalışır.

Demek istediğim, özyinelemeyi daima sistemin ölçeğini göz önünde bulundurarak akıllıca kullanmalıyız. Genellikle özyineleme, daha iyi ölçeklenen yinelemeli programa dönüştürülebilir. (Aynı algoritmanın yinelemeli bir versiyonu sayfanın alt kısmında verilmiştir, 9 milisaniyede 1 milyon büyüklüğünde tekil bağlantılı bir listeyi tersine çevirir.)

    private static LinkedListNode doReverseRecursively(LinkedListNode x, LinkedListNode first){

    LinkedListNode second = first.next;

    first.next = x;

    if(second != null){
        return doReverseRecursively(first, second);
    }else{
        return first;
    }
}

public static LinkedListNode reverseRecursively(LinkedListNode head){
    return doReverseRecursively(null, head);
}

Aynı Algoritmanın Yinelemeli Sürümü:

    public static LinkedListNode reverseIteratively(LinkedListNode head){
    return doReverseIteratively(null, head);
}   

private static LinkedListNode doReverseIteratively(LinkedListNode x, LinkedListNode first) {

    while (first != null) {
        LinkedListNode second = first.next;
        first.next = x;
        x = first;

        if (second == null) {
            break;
        } else {
            first = second;
        }
    }
    return first;
}


public static LinkedListNode reverseIteratively(LinkedListNode head){
    return doReverseIteratively(null, head);
}

JVM ile düşünüyorum, aslında dizüstü bilgisayarınızın özellikleri ne önemli değil.
kevin

3

A StackOverflowError, Java'daki bir çalışma zamanı hatasıdır.

JVM tarafından ayrılan çağrı yığını belleği miktarı aşıldığında atılır.

StackOverflowErrorAtılan bir yaygın durum, çağrı yığını aşırı derin veya sonsuz özyineleme nedeniyle aşıldığındadır.

Misal:

public class Factorial {
    public static int factorial(int n){
        if(n == 1){
            return 1;
        }
        else{
            return n * factorial(n-1);
        }
    }

    public static void main(String[] args){
        System.out.println("Main method started");
        int result = Factorial.factorial(-1);
        System.out.println("Factorial ==>"+result);
        System.out.println("Main method ended");
    }
}

Yığın izleme:

Main method started
Exception in thread "main" java.lang.StackOverflowError
at com.program.stackoverflow.Factorial.factorial(Factorial.java:9)
at com.program.stackoverflow.Factorial.factorial(Factorial.java:9)
at com.program.stackoverflow.Factorial.factorial(Factorial.java:9)

Yukarıdaki durumda, programlı değişiklikler yapılarak önlenebilir. Ancak program mantığı doğruysa ve yine de oluşursa, yığın boyutunun artırılması gerekir.


0

Bu tipik bir vaka olduğunu java.lang.StackOverflowError... yönteminde yinelemeli içinde çıkışı olmayan kendini çağırıyor doubleValue(), floatValue()vb

Rational.java

    public class Rational extends Number implements Comparable<Rational> {
        private int num;
        private int denom;

        public Rational(int num, int denom) {
            this.num = num;
            this.denom = denom;
        }

        public int compareTo(Rational r) {
            if ((num / denom) - (r.num / r.denom) > 0) {
                return +1;
            } else if ((num / denom) - (r.num / r.denom) < 0) {
                return -1;
            }
            return 0;
        }

        public Rational add(Rational r) {
            return new Rational(num + r.num, denom + r.denom);
        }

        public Rational sub(Rational r) {
            return new Rational(num - r.num, denom - r.denom);
        }

        public Rational mul(Rational r) {
            return new Rational(num * r.num, denom * r.denom);
        }

        public Rational div(Rational r) {
            return new Rational(num * r.denom, denom * r.num);
        }

        public int gcd(Rational r) {
            int i = 1;
            while (i != 0) {
                i = denom % r.denom;
                denom = r.denom;
                r.denom = i;
            }
            return denom;
        }

        public String toString() {
            String a = num + "/" + denom;
            return a;
        }

        public double doubleValue() {
            return (double) doubleValue();
        }

        public float floatValue() {
            return (float) floatValue();
        }

        public int intValue() {
            return (int) intValue();
        }

        public long longValue() {
            return (long) longValue();
        }
    }

Main.java

    public class Main {

        public static void main(String[] args) {

            Rational a = new Rational(2, 4);
            Rational b = new Rational(2, 6);

            System.out.println(a + " + " + b + " = " + a.add(b));
            System.out.println(a + " - " + b + " = " + a.sub(b));
            System.out.println(a + " * " + b + " = " + a.mul(b));
            System.out.println(a + " / " + b + " = " + a.div(b));

            Rational[] arr = {new Rational(7, 1), new Rational(6, 1),
                    new Rational(5, 1), new Rational(4, 1),
                    new Rational(3, 1), new Rational(2, 1),
                    new Rational(1, 1), new Rational(1, 2),
                    new Rational(1, 3), new Rational(1, 4),
                    new Rational(1, 5), new Rational(1, 6),
                    new Rational(1, 7), new Rational(1, 8),
                    new Rational(1, 9), new Rational(0, 1)};

            selectSort(arr);

            for (int i = 0; i < arr.length - 1; ++i) {
                if (arr[i].compareTo(arr[i + 1]) > 0) {
                    System.exit(1);
                }
            }


            Number n = new Rational(3, 2);

            System.out.println(n.doubleValue());
            System.out.println(n.floatValue());
            System.out.println(n.intValue());
            System.out.println(n.longValue());
        }

        public static <T extends Comparable<? super T>> void selectSort(T[] array) {

            T temp;
            int mini;

            for (int i = 0; i < array.length - 1; ++i) {

                mini = i;

                for (int j = i + 1; j < array.length; ++j) {
                    if (array[j].compareTo(array[mini]) < 0) {
                        mini = j;
                    }
                }

                if (i != mini) {
                    temp = array[i];
                    array[i] = array[mini];
                    array[mini] = temp;
                }
            }
        }
    }

Sonuç

    2/4 + 2/6 = 4/10
    Exception in thread "main" java.lang.StackOverflowError
    2/4 - 2/6 = 0/-2
        at com.xetrasu.Rational.doubleValue(Rational.java:64)
    2/4 * 2/6 = 4/24
        at com.xetrasu.Rational.doubleValue(Rational.java:64)
    2/4 / 2/6 = 12/8
        at com.xetrasu.Rational.doubleValue(Rational.java:64)
        at com.xetrasu.Rational.doubleValue(Rational.java:64)
        at com.xetrasu.Rational.doubleValue(Rational.java:64)
        at com.xetrasu.Rational.doubleValue(Rational.java:64)
        at com.xetrasu.Rational.doubleValue(Rational.java:64)

İşte StackOverflowErrorOpenJDK 7 kaynak kodu


0

Bir crunch, Below durumu Yığın taşma hatası getirecektir.

public class Example3 {

public static void main(String[] args) {

    main(new String[1]);

}

}


-1

İşte bir örnek

public static void main(String[] args) {
    System.out.println(add5(1));
}

public static int add5(int a) {
    return add5(a) + 5;
}

Bir StackOverflowError temelde, büyük olasılıkla kendini çağıran ve sonsuzluk için devam eden (veya bir StackOverflowError verene kadar) bir şey yapmaya çalıştığınızdadır.

add5(a) kendini arayacak, sonra tekrar arayacak vb.


-1

"Yığın taşması (taşma)" terimi sıklıkla kullanılır, ancak yanlış adlandırma; saldırılar yığının üzerine taşmaz, ancak yığındaki arabellekleri.

- Prof. Dr. Dieter Gollmann'ın ders slaytlarından

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.