Что такое StackOverflowError?

Что такое StackOverflowError, что его вызывает, и как мне с ними бороться?

Ответ 1

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

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

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

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

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

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

Ответ 2

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

Локальная переменная хранится в стеке: 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. Этот флаг может быть указан либо через конфигурацию проектов, либо через командную строку. Формат аргумента -Xss: -Xss<size>[g|G|m|M|k|K]

Ответ 3

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

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

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

Ответ 4

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

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

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

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

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

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

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

Ответ 8

StackOverflowError относится к стеку, поскольку OutOfMemoryError относится к куче.

Неограниченные рекурсивные вызовы приводят к тому, что пространство стека израсходовано.

В следующем примере StackOverflowError:

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

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

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

Ответ 9

Вот пример рекурсивного алгоритма для обращения к односвязному списку. На ноутбуке со следующей спецификацией (память 4G, процессор Intel Core i5 2.3GHz, 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);
}

Ответ 10

StackOverflowError - это ошибка времени выполнения в Java.

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

Распространенным случаем 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)

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

Ответ 11

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

- с лекционных слайдов профессора Дитера Голлмана

Ответ 12

Здесь пример

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

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

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

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

Ответ 13

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

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

результат

    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

Ответ 14

StackOverFlowError - это одна из наиболее распространенных ошибок JVM.

enter image description here

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