Apa itu StackOverflowError?

439

Apa itu StackOverflowError, apa penyebabnya, dan bagaimana saya harus menghadapinya?

Ziggy
sumber
Ukuran tumpukan di java kecil. Dan beberapa kali seperti banyak panggilan rekursif Anda menghadapi masalah ini. Anda dapat mendesain ulang kode Anda secara berulang. Anda dapat menemukan pola desain umum untuk melakukannya di url ini: jndanial.com/73
JNDanial
Salah satu cara yang tidak jelas untuk mendapatkannya: tambahkan baris new Object() {{getClass().newInstance();}};ke beberapa konteks statis (misalnya mainmetode). Tidak berfungsi dari konteks instance (hanya melempar InstantiationException).
John McClane

Jawaban:

408

Parameter dan variabel lokal dialokasikan pada stack (dengan tipe referensi, objek tinggal di heap dan variabel dalam referensi stack yang objek di heap). Tumpukan biasanya hidup di ujung atas ruang alamat Anda dan saat digunakan mengarah ke bagian bawah ruang alamat (yaitu menuju nol).

Proses Anda juga memiliki tumpukan , yang hidup di bagian bawah proses Anda. Saat Anda mengalokasikan memori, tumpukan ini dapat tumbuh menuju ujung atas ruang alamat Anda. Seperti yang Anda lihat, ada potensi tumpukan "bertabrakan" dengan tumpukan (sedikit seperti lempeng tektonik !!!).

Penyebab umum untuk stack overflow adalah panggilan rekursif yang buruk . Biasanya, ini disebabkan ketika fungsi rekursif Anda tidak memiliki kondisi terminasi yang benar, sehingga akhirnya memanggil dirinya sendiri selamanya. Atau ketika kondisi penghentian baik-baik saja, itu dapat disebabkan oleh membutuhkan terlalu banyak panggilan rekursif sebelum memenuhinya.

Namun, dengan pemrograman GUI, dimungkinkan untuk menghasilkan rekursi tidak langsung . Misalnya, aplikasi Anda mungkin menangani pesan cat, dan, saat memprosesnya, ia dapat memanggil fungsi yang menyebabkan sistem mengirim pesan cat lain. Di sini Anda tidak secara eksplisit menyebut diri Anda, tetapi OS / VM telah melakukannya untuk Anda.

Untuk mengatasinya, Anda harus memeriksa kode Anda. Jika Anda memiliki fungsi yang menyebut diri mereka sendiri maka periksa apakah Anda memiliki kondisi terminating. Jika sudah, maka periksa bahwa ketika memanggil fungsi Anda setidaknya telah memodifikasi salah satu argumen, jika tidak maka tidak akan ada perubahan yang terlihat untuk fungsi yang dipanggil secara rekursif dan kondisi terminating tidak berguna. Juga perhatikan bahwa ruang stack Anda dapat kehabisan memori sebelum mencapai kondisi terminasi yang valid, jadi pastikan metode Anda dapat menangani nilai input yang membutuhkan lebih banyak panggilan rekursif.

Jika Anda tidak memiliki fungsi rekursif yang jelas maka periksa untuk melihat apakah Anda memanggil fungsi pustaka yang secara tidak langsung akan menyebabkan fungsi Anda dipanggil (seperti kasus tersirat di atas).

Sean
sumber
1
Poster asli: hei ini bagus. Jadi rekursi selalu bertanggung jawab atas stack overflow? Atau bisakah hal-hal lain juga bertanggung jawab untuk mereka? Sayangnya saya menggunakan perpustakaan ... tapi bukan yang saya mengerti.
Ziggy
4
Ha ha ha, jadi ini dia: while (poin <100) {addMouseListeners (); moveball (); checkforcollision (); jeda (kecepatan);} Wow saya merasa lumpuh karena tidak menyadari bahwa saya akan berakhir dengan setumpuk pendengar tikus ... Terima kasih teman-teman!
Ziggy
4
Tidak, stack overflows juga dapat berasal dari variabel yang terlalu besar untuk dialokasikan pada stack jika Anda mencari artikel Wikipedia di dalamnya di en.wikipedia.org/wiki/Stack_overflow .
JB King
8
Harus ditunjukkan bahwa hampir tidak mungkin untuk "menangani" kesalahan stack overflow. Di sebagian besar lingkungan, untuk menangani kesalahan kita perlu menjalankan kode pada stack, yang sulit jika tidak ada lagi ruang stack.
Hot Licks
3
@JB King: Tidak benar-benar berlaku untuk Java, di mana hanya tipe dan referensi primitif yang disimpan di stack. Semua hal besar (array dan objek) ada di heap.
jcsahnwaldt mengatakan GoFundMonica
107

Untuk menggambarkan ini, pertama mari kita pahami bagaimana variabel dan objek lokal disimpan.

Variabel lokal disimpan dalam tumpukan : masukkan deskripsi gambar di sini

Jika Anda melihat gambar Anda harus dapat memahami bagaimana segala sesuatunya bekerja.

Ketika panggilan fungsi dipanggil oleh aplikasi Java, bingkai tumpukan dialokasikan pada tumpukan panggilan. Bingkai tumpukan berisi parameter dari metode yang dipanggil, parameter lokalnya, dan alamat pengembalian metode. Alamat kembali menunjukkan titik eksekusi dari mana, eksekusi program akan dilanjutkan setelah metode yang dipanggil kembali. Jika tidak ada ruang untuk bingkai tumpukan baru, maka StackOverflowErrordilemparkan oleh Java Virtual Machine (JVM).

Kasus paling umum yang mungkin dapat menghabiskan tumpukan aplikasi Java adalah rekursi. Dalam rekursi, metode memanggil dirinya sendiri selama eksekusi. Rekursi dianggap sebagai teknik pemrograman tujuan umum yang kuat tetapi harus digunakan dengan hati-hati, untuk menghindari StackOverflowError.

Contoh melempar a StackOverflowErrorditunjukkan di bawah ini:

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);
  }
}

Dalam contoh ini, kami mendefinisikan metode rekursif, yang disebut recursivePrintyang mencetak bilangan bulat dan kemudian, memanggil dirinya sendiri, dengan bilangan bulat berikutnya sebagai argumen. Rekursi berakhir sampai kita lulus 0sebagai parameter. Namun, dalam contoh kami, kami meneruskan dalam parameter dari 1 dan peningkatan pengikut, akibatnya, rekursi tidak akan pernah berakhir.

Eksekusi sampel, menggunakan -Xss1Mbendera yang menentukan ukuran tumpukan ulir sama dengan 1MB, ditunjukkan di bawah ini:

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)
        ...

Tergantung pada konfigurasi awal JVM, hasilnya mungkin berbeda, tetapi akhirnya StackOverflowErrorharus dibuang. Contoh ini adalah contoh yang sangat baik tentang bagaimana rekursi dapat menyebabkan masalah, jika tidak diterapkan dengan hati-hati.

Cara menangani StackOverflowError

  1. Solusi paling sederhana adalah dengan hati-hati memeriksa jejak tumpukan dan mendeteksi pola pengulangan nomor garis. Nomor-nomor baris ini menunjukkan kode yang dipanggil secara rekursif. Setelah Anda mendeteksi garis-garis ini, Anda harus hati-hati memeriksa kode Anda dan memahami mengapa rekursi tidak pernah berakhir.

  2. Jika Anda telah memverifikasi bahwa rekursi diimplementasikan dengan benar, Anda dapat meningkatkan ukuran tumpukan, untuk memungkinkan jumlah doa yang lebih besar. Bergantung pada Java Virtual Machine (JVM) yang diinstal, ukuran tumpukan ulir default mungkin sama dengan 512KB, atau 1MB . Anda dapat meningkatkan ukuran tumpukan ulir menggunakan -Xssbendera. Bendera ini dapat ditentukan baik melalui konfigurasi proyek, atau melalui baris perintah. Format -Xssargumennya adalah: -Xss<size>[g|G|m|M|k|K]

Varun
sumber
Tampaknya ada bug di beberapa versi java ketika menggunakan windows di mana argumen -Xss hanya berlaku pada utas baru
goerlibe
65

Jika Anda memiliki fungsi seperti:

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

Kemudian foo () akan terus memanggil dirinya sendiri, semakin dalam dan semakin dalam, dan ketika ruang yang digunakan untuk melacak fungsi apa yang Anda isi, Anda mendapatkan kesalahan stack overflow.

Khoth
sumber
12
Salah. Fungsi Anda adalah rekursif ekor. Sebagian besar bahasa yang dikompilasi memiliki optimasi rekursi ekor. Ini berarti rekursi berkurang menjadi loop sederhana dan Anda tidak akan pernah menekan stack overflow dengan potongan kode ini pada beberapa sistem.
Ceria
Ceria, bahasa nonfungsional mana yang mendukung rekursi ekor?
horseyguy
@banister dan beberapa implementasi javascript
Pacerier
@horseyguy Scala memiliki dukungan untuk rekursi Tail.
Ajit K'sagar
Ini menangkap esensi dari apa yang dapat membuat stack overflow. Bagus.
Pixel
24

Stack overflow berarti persis seperti itu: stack overflow. Biasanya ada satu tumpukan dalam program yang berisi variabel lingkup lokal dan alamat tempat mengembalikan ketika eksekusi rutin berakhir. Tumpukan itu cenderung menjadi rentang memori tetap di suatu tempat di memori, oleh karena itu terbatas berapa banyak yang bisa mengandung nilai.

Jika stack kosong Anda tidak bisa pop, jika Anda melakukannya Anda akan mendapatkan kesalahan stack underflow.

Jika tumpukan penuh Anda tidak bisa mendorong, jika Anda melakukannya Anda akan mendapatkan kesalahan stack overflow.

Jadi stack overflow muncul di mana Anda mengalokasikan terlalu banyak ke dalam stack. Misalnya, dalam rekursi yang disebutkan.

Beberapa implementasi mengoptimalkan beberapa bentuk rekursi. Rekursi ekor khususnya. Tail rutin rekursif adalah bentuk rutin di mana panggilan rekursif muncul sebagai hal terakhir yang rutin dilakukan. Panggilan rutin semacam itu hanya dikurangi menjadi lompatan.

Beberapa implementasi sejauh mengimplementasikan tumpukan mereka sendiri untuk rekursi, oleh karena itu mereka memungkinkan rekursi untuk berlanjut sampai sistem kehabisan memori.

Hal termudah yang bisa Anda coba adalah meningkatkan ukuran tumpukan Anda jika Anda bisa. Jika Anda tidak dapat melakukannya, hal terbaik kedua adalah melihat apakah ada sesuatu yang jelas menyebabkan stack overflow. Cobalah dengan mencetak sesuatu sebelum dan sesudah panggilan menjadi rutin. Ini membantu Anda mengetahui rutinitas yang gagal.

Riang
sumber
4
Apakah ada yang namanya stack underflow ?
Pacerier
5
Underflow tumpukan dimungkinkan dalam perakitan (muncul lebih dari yang Anda dorong), meskipun dalam bahasa yang dikompilasi, itu hampir mustahil. Saya tidak yakin, Anda mungkin dapat menemukan implementasi dari alokasi C () yang "mendukung" ukuran negatif.
Score_Under
2
Stack overflow berarti persis seperti itu: stack overflow. Biasanya ada satu tumpukan dalam program yang berisi variabel lingkup lokal -> Tidak, setiap utas memiliki tumpukan sendiri yang berisi bingkai tumpukan untuk setiap pemanggilan metode yang berisi variabel lokal ..
Koray Tugay
9

Stack overflow biasanya disebut dengan panggilan fungsi nesting terlalu dalam (terutama mudah saat menggunakan rekursi, yaitu fungsi yang memanggil dirinya sendiri) atau mengalokasikan sejumlah besar memori pada stack di mana menggunakan heap akan lebih tepat.

Greg
sumber
1
Ups, tidak melihat tag Java
Greg
Juga, dari poster asli di sini: fungsi bersarang terlalu dalam pada apa? Fungsi lainnya? Dan: bagaimana cara mengalokasikan memori ke stack atau heap (karena, Anda tahu, saya jelas telah melakukan salah satu dari hal-hal ini tanpa mengetahui).
Ziggy
@ Ziggy: Ya, jika satu fungsi memanggil fungsi lain, yang memanggil fungsi lain, dan seterusnya, setelah banyak level, program Anda akan mengalami stack overflow. [berlanjut]
Chris Jester-Young
[... lanjutan] Di Jawa, Anda tidak dapat secara langsung mengalokasikan memori dari stack (sedangkan di C, Anda bisa, dan ini akan menjadi sesuatu yang harus diperhatikan), jadi itu tidak mungkin menjadi penyebabnya. Di Jawa, semua alokasi langsung berasal dari heap, dengan menggunakan "baru".
Chris Jester-Young
@ ChrisJester-Young Bukankah benar bahwa jika saya memiliki 100 variabel lokal dalam suatu metode, semuanya berjalan di stack tanpa pengecualian?
Pacerier
7

Seperti yang Anda katakan, Anda perlu menunjukkan beberapa kode. :-)

Kesalahan stack overflow biasanya terjadi ketika fungsi Anda memanggil sarang terlalu dalam. Lihat utas Kode Stack Overflow Golf untuk beberapa contoh bagaimana hal ini terjadi (meskipun dalam kasus pertanyaan itu, jawabannya sengaja menyebabkan stack overflow).

Chris Jester-Young
sumber
1
Saya benar-benar ingin menambahkan kode, tetapi karena saya tidak tahu apa yang menyebabkan stack overflows, saya tidak yakin kode apa yang harus ditambahkan. menambahkan semua kode akan timpang, bukan?
Ziggy
Apakah proyek Anda open-source? Jika demikian, cukup buat akun Sourceforge atau github, dan unggah semua kode Anda di sana. :-)
Chris Jester-Young
ini kedengarannya ide yang bagus, tetapi saya adalah noob sehingga saya bahkan tidak tahu apa yang harus saya unggah. Seperti, perpustakaan yang saya impor kelas yang saya perluas dll ... semua tidak diketahui oleh saya. Oh man: masa-masa sulit.
Ziggy
5

Penyebab paling umum dari stack overflow adalah rekursi yang terlalu dalam atau tidak terbatas . Jika ini adalah masalah Anda, tutorial tentang Rekursi Jawa ini dapat membantu memahami masalahnya.

percikan
sumber
5

StackOverflowErroradalah ke tumpukan seperti OutOfMemoryErrorke tumpukan.

Panggilan rekursif tanpa batas menghasilkan ruang stack yang digunakan.

Contoh berikut menghasilkan StackOverflowError:

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

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

StackOverflowError dapat dihindari jika panggilan rekursif dibatasi untuk mencegah total agregat dari panggilan dalam memori yang tidak lengkap (dalam byte) dari melebihi ukuran stack (dalam byte).

Vikram
sumber
3

Berikut adalah contoh algoritma rekursif untuk membalikkan daftar yang terhubung sendiri. Pada laptop dengan spesifikasi berikut (memori 4G, Intel Core i5 2.3GHz CPU, 64 bit Windows 7), fungsi ini akan mengalami kesalahan StackOverflow untuk daftar ukuran yang ditautkan mendekati 10.000.

Maksud saya adalah bahwa kita harus menggunakan rekursi secara bijak, selalu mempertimbangkan skala sistem. Seringkali rekursi dapat dikonversi ke program berulang, yang skala lebih baik. (Satu versi berulang dari algoritma yang sama diberikan di bagian bawah halaman, itu membalikkan daftar ukuran 1 juta dalam 9 milidetik.

    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);
}

Versi berulang dari Algoritma yang Sama:

    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);
}
Yiling
sumber
Saya pikir dengan JVM, tidak masalah apa spesifikasi laptop Anda.
kevin
3

A StackOverflowErroradalah kesalahan runtime di java.

Itu dilemparkan ketika jumlah memori tumpukan panggilan yang dialokasikan oleh JVM terlampaui.

Kasus umum StackOverflowErroryang dilemparkan, adalah ketika tumpukan panggilan melebihi karena rekursi dalam atau tak terbatas yang berlebihan.

Contoh:

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");
    }
}

Jejak tumpukan:

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)

Dalam kasus di atas, dapat dihindari dengan melakukan perubahan terprogram. Tetapi jika logika program benar dan masih terjadi maka ukuran stack Anda perlu ditingkatkan.

Rahul Sah
sumber
0

Ini kasus khas java.lang.StackOverflowError... Metode ini secara rekursif menyebut dirinya tanpa keluar di doubleValue(), floatValue(), dll

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;
                }
            }
        }
    }

Hasil

    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)

Berikut adalah kode sumber StackOverflowErrordi OpenJDK 7


sumber
0

Dalam krisis, situasi di bawah ini akan membawa kesalahan Stack overflow.

public class Example3 {

public static void main(String[] args) {

    main(new String[1]);

}

}

Kaliappan
sumber
-1

Ini sebuah contoh

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

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

StackOverflowError pada dasarnya adalah ketika Anda mencoba melakukan sesuatu, yang kemungkinan besar memanggil dirinya sendiri, dan berlanjut hingga tak terbatas (atau sampai ia memberikan StackOverflowError).

add5(a) akan memanggil dirinya sendiri, dan kemudian memanggil dirinya sendiri lagi, dan seterusnya.

John S.
sumber
-1

Istilah "stack overrun (overflow)" sering digunakan tetapi keliru; serangan tidak meluap stack tetapi buffer di stack.

- dari slide ceramah Prof. Dr. Dieter Gollmann

Sergiu
sumber