Что такое StackOverflowError?

что это StackOverflowError, что вызывает это, и как я должен с ними бороться?

13 ответов


параметры и локальные переменные выделяется на стек (со ссылочными типами объект живет на кучу и переменная ссылается на этот объект). Стек обычно живет в верхний конец вашего адресного пространства и по мере его использования он направляется к дно адресного пространства (т. е. к нулю).

ваш процесс также имеет кучу, который живет в дно конце процесс. По мере выделения памяти эта куча может расти к верхнему концу адресного пространства. Как вы можете видеть, существует потенциал для кучи "наехать" со стеком (немного похоже на тектонические плиты!!!).

общей причиной переполнения стека является плохой рекурсивный вызов. Как правило, это вызвано тем, что рекурсивные функции не имеют правильного условия завершения, поэтому он вызывает себя навсегда.

однако, с GUI программирование, можно генерировать косвенная рекурсия. Например, приложение может обрабатывать сообщения paint и при их обработке вызывать функцию, которая заставляет систему отправлять другое сообщение paint. Здесь вы явно не вызывали себя, но OS / VM сделала это за вас.

чтобы справиться с ними, вам нужно будет изучить свой код. Если у вас есть функции, которые называют себя, проверьте, что у вас есть условие завершения. Если да, то проверьте, что при вызове функции вы хотя бы изменили один из аргументов, иначе не будет видимых изменений для рекурсивно вызываемой функции, и условие завершения бесполезно.

Если у вас нет очевидных рекурсивных функций, проверьте, вызываете ли вы какие-либо библиотечные функции, которые косвенно вызовет вызов вашей функции (например, неявный случай выше).


чтобы описать это, сначала давайте поймем, как местные переменные и объекты.

локальные переменные хранятся в стек: enter image description here

если вы посмотрите на изображение, вы должны быть в состоянии понять, как вещи работают.

когда вызов функции вызывается приложением Java, в стеке вызовов выделяется кадр стека. Фрейм стека содержит параметры вызываемого метода, его локальный параметры и обратный адрес метода. Обратный адрес обозначает точку выполнения, с которой выполнение программы должно продолжаться после возвращения вызванного метода. Если нет места для нового кадра стека, то StackOverflowError выбрасывается виртуальной машиной Java (JVM).

наиболее распространенным случаем, который может исчерпать стек приложения Java, является рекурсия. В рекурсии метод вызывает себя во время выполнения. Рекурсия является мощным универсальный метод программирования, но должен использоваться с осторожностью, чтобы избежать StackOverflowError.

пример кидания StackOverflowError показано ниже:

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

в этом примере мы определяем рекурсивный метод, называемый recursivePrint который печатает целое число, а затем вызывает себя со следующим последовательным целым числом в качестве аргумента. Рекурсия заканчивается, пока мы не перейдем в 0 в качестве параметра. Однако, в нашем примере, мы передали параметр из 1 и его возрастающих последователей, следовательно, рекурсия никогда не закончится.

пример выполнения, используя -Xss1M флаг, указывающий размер стека потоков, равный 1 МБ, показан ниже:

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 результаты могут отличаться, но в конечном итоге StackOverflowError будет брошено. Этот пример является очень хорошим примером того, как рекурсия может вызвать проблемы, если не реализованы осторожность.

как бороться с StackOverflowError

  1. самое простое решение-тщательно проверить трассировку стека и обнаружьте повторяющийся шаблон номеров строк. Эти номера строк укажите код, вызываемый рекурсивно. Как только вы обнаружите это строки, вы должны тщательно проверить свой код и понять, почему рекурсия никогда не заканчивается.

  2. если вы убедились, что рекурсия реализовано правильно, вы можете увеличить размер стека, в чтобы разрешить большее количество вызовов. В зависимости от Java Установлена виртуальная машина (JVM), размер стека потоков по умолчанию может равным 512КБ или 1МБ. Можно увеличить стек потоков размер с помощью -Xss флаг. Этот флаг можно указать либо через конфигурация проекта, или через командную строку. Формат


Если у вас есть функция как:

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

тогда foo () будет продолжать вызывать себя, все глубже и глубже, и когда пространство, используемое для отслеживания того, какие функции вы используете, заполняется, вы получаете ошибку переполнения стека.


переполнение стека означает именно это: переполнение стека. Обычно в программе есть один стек, который содержит переменные локальной области и адреса, куда возвращаться при завершении выполнения подпрограммы. Этот стек имеет тенденцию быть фиксированным диапазоном памяти где-то в памяти, поэтому он ограничен тем, сколько он может содержать значения.

Если стек пуст, вы не можете поп, если вы это сделаете, вы получите ошибку стека underflow.

Если стек заполнен, вы не можете нажать, если вы это сделаете вы получите ошибку переполнения стека.

таким образом, переполнение стека появляется там, где вы выделяете слишком много в стек. Например, в упомянутой рекурсии.

некоторые реализации оптимизируют некоторые формы рекурсий. В частности, хвостовая рекурсия. Хвост рекурсивные подпрограммы-это форма подпрограмм, где рекурсивный вызов появляется как последнее, что делает подпрограмма. Такой рутинный вызов просто сводится к прыжку.

некоторые реализации заходят так далеко, что реализуют их собственные стеки для рекурсии, поэтому они позволяют рекурсии продолжаться до тех пор, пока в системе не закончится память.

самое простое, что вы могли бы попробовать, это увеличить размер стека, если сможете. Если вы не можете этого сделать, второе, что лучше всего было бы посмотреть, есть ли что-то, что явно вызывает переполнение стека. Попробуйте, распечатав что-то до и после вызова в рутину. Это поможет вам выяснить, что происходит.


переполнение стека обычно вызывается слишком глубокими вызовами функции вложенности (особенно легко при использовании рекурсии, т. е. функции, которая вызывает себя) или выделением большого объема памяти в стеке, где использование кучи было бы более уместным.


Как вы говорите, вам нужно показать какой-то код. :-)

ошибка переполнения стека обычно происходит, когда ваша функция вызывает nest слишком глубоко. Вижу Код Переполнения Стека Golf поток для некоторых примеров того, как это происходит (хотя в случае этого вопроса ответы намеренно вызывают переполнение стека).


наиболее распространенной причиной переполнения стека является чрезмерно глубокая или бесконечная рекурсия. Если это ваша проблема, этот учебник о рекурсии Java может помочь понять проблему.


StackOverflowError находится в стеке, как OutOfMemoryError находится в куче.

неограниченные рекурсивные вызовы приводят к использованию пространства стека.

следующий пример производит StackOverflowError:

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

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

StackOverflowError можно избежать, если рекурсивные вызовы ограничены, чтобы предотвратить превышение суммарного количества неполных вызовов в памяти (в байтах) размера стека (в байтах).


вот пример рекурсивного алгоритма для реверсирования односвязного списка. На ноутбуке со следующей спецификацией (память 4G, процессор Intel Core i5 2.3 GHz, 64 бит Windows 7) эта функция будет работать с ошибкой StackOverflow для связанного списка размера, близкого к 10,000.

Я считаю, что мы должны использовать рекурсию разумно, всегда принимая во внимание масштаб системы. Часто рекурсию можно преобразовать в итеративную программу, которая лучше масштабируется. (Одна итерация версия того же алгоритма приведена в нижней части страницы, она отменяет односвязный список размером 1 миллион за 9 миллисекунд.)

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

итеративная версия того же алгоритма:

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

A StackOverflowError является ошибкой времени выполнения в java.

он выбрасывается при превышении объема памяти стека вызовов, выделенного JVM.

обычный случай a StackOverflowErrorвыбрасывается, когда стек вызовов превышает из-за чрезмерной глубокой или бесконечной рекурсии.

пример:

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

трассировка стека:

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)

в вышеуказанном случае можно избежать выполнения программных изменений. Но если логика программы правильная и все же она происходит то, что вам нужно увеличить размер стека.


вот пример

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

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

StackOverflowError в основном, когда вы пытаетесь что-то сделать, что, скорее всего, называет себя и продолжается бесконечно (или пока он не дает StackOverflowError).

add5(a) вызовет себя, а затем вызовет себя снова и так далее.


термин" переполнение стека (переполнение) " часто используется, но неправильно; атаки не переполняют стек, а буферы в стеке.

-- из лекционных слайдов Проф. Д-Р Dieter Gollmann


это типичный случай java.lang.StackOverflowError... Метод рекурсивно вызывает себя без выхода в doubleValue(), floatValue(), etc.

рациональное.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;
                }
            }
        }
    }

результат

    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)

здесь код StackOverflowError в OpenJDK 7