development

StackOverflowError는 무엇입니까?

big-blog 2020. 2. 20. 23:31
반응형

StackOverflowError는 무엇입니까?


이란 무엇이며 StackOverflowError원인 은 무엇 이며 어떻게 처리해야하나요?


매개 변수 및 로컬 변수는 스택에 할당됩니다 (참조 유형의 경우 오브젝트는 에 있고 스택의 변수 에서 해당 오브젝트를 참조합니다). 스택은 일반적으로 주소 공간 상단에 존재하며 사용 되면 주소 공간 맨 아래 (예 : 0)로 향합니다.

프로세스에는 이 있으며 프로세스 끝에 있습니다. 메모리를 할당 할 때이 힙은 주소 공간의 상단으로 갈 수 있습니다. 보시다시피, 힙 이 스택과 "충돌" 할 가능성이 있습니다 (지각 판과 약간 비슷합니다 !!!).

스택 오버플로의 일반적인 원인은 잘못된 재귀 호출 입니다. 일반적으로 이것은 재귀 함수에 올바른 종료 조건이 없기 때문에 발생하므로 영원히 호출됩니다. 또는 종료 조건이 양호하면 완료하기 전에 너무 많은 재귀 호출을 요구하여 발생할 수 있습니다.

그러나 GUI 프로그래밍을 사용하면 간접 재귀 를 생성 할 수 있습니다 . 예를 들어, 앱에서 페인트 메시지를 처리하고있을 때 처리하는 동안 시스템에서 다른 페인트 메시지를 보내도록하는 함수를 호출 할 수 있습니다. 여기서 당신은 명시 적으로 자신을 부르지는 않았지만 OS / VM이 당신을 위해 그것을했습니다.

이를 처리하려면 코드를 검사해야합니다. 스스로 호출하는 함수가 있다면 종료 조건이 있는지 확인하십시오. 그렇다면 함수를 호출 할 때 적어도 하나의 인수를 수정했는지 확인하십시오. 그렇지 않으면 재귀 적으로 호출되는 함수에 대한 눈에 띄는 변화가 없으며 종료 조건은 쓸모가 없습니다. 또한 유효한 종료 조건에 도달하기 전에 스택 공간에 메모리가 부족할 수 있으므로 메소드가 더 재귀 호출이 필요한 입력 값을 처리 할 수 ​​있는지 확인하십시오.

재귀 함수가 분명하지 않은 경우 함수가 간접적으로 호출되는 라이브러리 함수를 호출하는지 확인하십시오 (위의 암시 적 경우와 같이).


이것을 설명하기 위해 먼저 지역 변수와 객체가 어떻게 저장 되는지 이해하자 .

지역 변수는 스택 에 저장됩니다 :여기에 이미지 설명을 입력하십시오

이미지를 살펴보면 일이 어떻게 작동하는지 이해할 수 있어야합니다.

Java 애플리케이션이 함수 호출을 호출하면 스택 프레임이 호출 스택에 할당됩니다. 스택 프레임에는 호출 된 메소드의 매개 변수, 로컬 매개 변수 및 메소드의 리턴 주소가 포함됩니다. 리턴 주소는 호출 된 메소드가 리턴 된 후 프로그램 실행이 계속되는 실행 지점을 나타냅니다. 새 스택 프레임을위한 공간이 없으면 StackOverflowErrorJVM (Java Virtual Machine)에서 발생합니다.

Java 애플리케이션의 스택을 소진시킬 수있는 가장 일반적인 경우는 재귀입니다. 재귀에서 메소드는 실행 중에 자신을 호출합니다. 재귀는 강력한 범용 프로그래밍 기술로 간주되지만 피하기 위해주의해서 사용해야합니다 StackOverflowError.

a를 던지는 예 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스레드 스택의 크기를 1MB로 지정 하는 플래그를 사용한 샘플 실행 은 다음과 같습니다.

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. 재귀가 올바르게 구현되었는지 확인한 경우 더 많은 호출을 허용하기 위해 스택 크기를 늘릴 수 있습니다. 설치된 JVM (Java Virtual Machine)에 따라 기본 스레드 스택 크기는 512KB 또는 1MB 일 수 있습니다. -Xss플래그를 사용하여 스레드 스택 크기를 늘릴 수 있습니다 . 이 플래그는 프로젝트 구성 또는 명령 행을 통해 지정할 수 있습니다. -Xss인수 의 형식 은 다음과 같습니다.-Xss<size>[g|G|m|M|k|K]


다음과 같은 기능이 있다면 :

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

그런 다음 foo ()는 계속해서 자신을 호출하고 점점 더 깊어지고 어떤 함수를 추적하는 데 사용되는 공간이 채워지면 스택 오버플로 오류가 발생합니다.


스택 오버플로는 정확히 다음을 의미합니다. 스택 오버플로. 일반적으로 로컬 범위 변수를 포함하고 루틴 실행이 종료 될 때 리턴 할 주소를 포함하는 하나의 스택이 프로그램에 있습니다. 이 스택은 메모리 어딘가에 고정 메모리 범위 인 경향이 있으므로 값을 포함 할 수있는 양이 제한됩니다.

스택이 비어 있으면 팝을 할 수 없으며, 스택 언더 플로 오류가 발생합니다.

스택이 가득 찬 경우 푸시 할 수 없으며, 스택 오버플로 오류가 발생합니다.

따라서 스택에 너무 많이 할당 할 때 스택 오버플로가 나타납니다. 예를 들어, 언급 된 재귀에서.

일부 구현은 일부 형태의 재귀를 최적화합니다. 특히 꼬리 재귀. 테일 재귀 루틴은 재귀 호출이 루틴이 수행하는 최종 작업으로 나타나는 루틴 형태입니다. 이러한 일상적인 호출은 단순히 점프로 줄어 듭니다.

일부 구현은 재귀를 위해 자체 스택을 구현하는 한 시스템의 메모리가 부족할 때까지 재귀를 계속할 수 있습니다.

가능한 가장 쉬운 방법은 스택 크기를 늘리는 것입니다. 그래도 할 수 없다면, 두 번째 가장 좋은 방법은 스택 오버플로를 분명히 일으키는 것이 있는지 확인하는 것입니다. 전화 통화 전후에 무언가를 인쇄하여 사용해보십시오. 이것은 실패한 루틴을 찾는 데 도움이됩니다.


스택 오버플로는 일반적으로 중첩 함수 호출이 너무 깊게 (특히 재귀를 사용하는 경우 (즉, 자체 호출하는 경우) 쉽게) 중첩되거나 힙을 사용하는 것이 더 적합한 스택에 많은 양의 메모리를 할당함으로써 호출됩니다.


말했듯이 코드를 보여줘야합니다. :-)

스택 오버플로 오류는 일반적으로 함수 호출이 너무 깊을 때 발생합니다. 이것이 어떻게 발생하는지에 대한 몇 가지 예 스택 오버플로 코드 골프 스레드를 참조하십시오 (질문의 경우 대답은 의도적으로 스택 오버플로를 유발합니다).


스택 오버플로의 가장 일반적인 원인은 지나치게 깊거나 무한한 재귀 입니다. 이것이 문제점 인 경우 Java 재귀에 대한이 학습서 가 문제점을 이해하는 데 도움이 될 수 있습니다.


StackOverflowErrorOutOfMemoryError과 마찬가지로 스택에 있습니다.

무한 재귀 호출로 인해 스택 공간이 소모됩니다.

다음 예제는 다음을 생성합니다 StackOverflowError.

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

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

StackOverflowError 불완전한 메모리 내 총 호출 (바이트)이 스택 크기 (바이트)를 초과하지 않도록 재귀 호출을 바인드하면 피할 수 있습니다.


다음은 단일 연결 목록을 되 돌리는 재귀 알고리즘의 예입니다. 다음 사양 (4G 메모리, Intel Core i5 2.3GHz CPU, 64 비트 Windows 7)을 갖춘 랩톱에서이 기능은 10,000에 가까운 크기의 링크 된 목록에 대해 StackOverflow 오류가 발생합니다.

내 요점은 항상 시스템의 규모를 고려하여 재귀를 신중하게 사용해야한다는 것입니다. 종종 재귀를 반복 프로그램으로 변환하여 확장 성이 더 좋습니다. (동일한 알고리즘의 반복 버전 하나가 페이지의 맨 아래에 제공되며, 9 밀리 초의 단일 링크 크기 목록은 1 백만입니다.)

    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이 할당 한 호출 스택 메모리의 양을 초과하면 발생합니다.

발생하는 일반적인 경우는 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) 전화를 걸고 다시 전화를 걸 것입니다.


이것은 전형적인 경우입니다 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)

StackOverflowErrorOpenJDK 7 의 소스 코드는 다음과 같습니다.


"스택 오버런 (오버플로)"이라는 용어는 종종 사용되지만 잘못된 이름입니다. 공격은 스택 오버플로가 아니라 스택의 버퍼입니다.

-Dieter Gollmann 박사 의 강의 슬라이드에서

참고 URL : https://stackoverflow.com/questions/214741/what-is-a-stackoverflowerror



반응형