Что такое StackOverflowError?


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

13 358

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), размер стека потоков по умолчанию может равно либо 512KB, или 1MB. Вы можете увеличить стек потоков размер с помощью -Xss флаг. Этот флаг может быть указан либо через конфигурация проекта, или через командную строку. Формат

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

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

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

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

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

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

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

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

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

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

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

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

ошибка переполнения стека обычно происходит, когда функция вызывает гнездо слишком глубоко. Смотрите Stack Overflow Code 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 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() и т. д.

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

Главная.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