Вопрос

Что такое StackOverflowError, что является причиной этого и как я должен с ними бороться?

Это было полезно?

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

StackOverflowError в стек, как OutOfMemoryError в кучу.

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

В следующем примере показано <=>:

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

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

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

Здесь приведен пример рекурсивного алгоритма обращения к односвязному списку. На ноутбуке со следующими характеристиками (память 4G, процессор Intel Core i5 2,3 ГГц, 64-разрядная ОС Windows 7) эта функция будет приводить к ошибке StackOverflow для связанного списка размером, близким к 10000.

Я хочу сказать, что мы должны использовать рекурсию разумно, всегда принимая во внимание масштаб системы. Часто рекурсию можно преобразовать в итеративную программу, которая лучше масштабируется. (Одна итерационная версия того же алгоритма приводится внизу страницы, он переворачивает односвязный список размером 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.

Типичным случаем выбрасывания <=> является превышение стека вызовов из-за чрезмерной глубокой или бесконечной рекурсии.

Пример:

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) вызовет сам себя, затем снова вызовет себя и т. д.

Это типичный случай 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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top