sourcecode

Stack Overflow Error란 무엇입니까?

copyscript 2022. 7. 26. 23:32
반응형

Stack Overflow Error란 무엇입니까?

, 아, 아, 아, 아, 아, 아, 아, 아, 아.StackOverflowError원인이 무엇인지, 그리고 어떻게 대처해야 합니까?

파라미터와 로컬 변수는 스택에 할당됩니다(참조 유형에서는 객체가 에 존재하며 스택 내의 변수는 해당 힙에 있는 객체를 참조합니다).스택은 일반적으로 주소 공간의 상단 끝에 존재하며, 사용 빈도가 높아지면 주소 공간의 하단(즉, 0을 향함)으로 향합니다.

프로세스에는 프로세스의 하단에 존재하는 도 있습니다.메모리를 할당하면, 이 힙은 주소 공간의 상단을 향해 커질 수 있습니다.보시는 바와 같이 힙이 스택과 "충돌"할 가능성이 있습니다(약간의 구조판!!).

스택 오버플로의 일반적인 원인은 불량 재귀 콜입니다.일반적으로 이 문제는 재귀 함수에 올바른 종료 조건이 설정되어 있지 않기 때문에 결국 자신을 영원히 호출하게 되는 경우에 발생합니다.또는 종료 조건이 정상일 경우, 종료 조건을 충족하기 전에 재귀 콜이 너무 많이 필요하기 때문에 발생할 수 있습니다.

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

그들을 처리하려면 코드를 조사해야 합니다.자신을 호출하는 함수가 있는 경우 종료 조건이 있는지 확인합니다.이 경우 함수를 호출할 때 인수 중 적어도1개가 변경되었는지 확인합니다.변경하지 않으면 재귀적으로 호출된 함수에 대한 가시적인 변경은 없으며 종료 조건은 사용할 수 없습니다.또, 유효한 종료 조건에 도달하기 전에, 스택 영역이 부족할 가능성이 있기 때문에, 보다 재귀적인 콜을 필요로 하는 입력치를 메서드로 처리할 수 있는 것을 확인해 주세요.

명백한 재귀 함수가 없는 경우, 간접적으로 함수를 호출하는 라이브러리 함수를 호출하고 있는지 확인합니다(위의 암묵적인 경우).

이를 설명하기 위해 먼저 로컬 변수와 객체가 어떻게 저장되는지 살펴보겠습니다.

로컬 변수는 스택에 저장됩니다.

여기에 이미지 설명을 입력하십시오.

이미지를 보면 어떻게 작동하는지 알 수 있을 거예요.

함수 콜이 Java 응용 프로그램에 의해 호출되면 스택프레임이 콜스택에 할당됩니다.스택 프레임에는 호출된 메서드의 파라미터, 로컬파라미터 및 메서드의 반환 주소가 포함됩니다.반환 주소는 호출된 메서드가 반환된 후 프로그램 실행이 계속되어야 하는 실행 지점을 나타냅니다.을 위한 " " " 는 " " " 입니다.StackOverflowErrorJava Virtual Machine(JVM; Java 가상 머신)입니다.

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스레드 스택의 크기를 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 가 문제를 것을 보여주는 입니다.이 예는 주의해서 구현하지 않으면 재귀로 인해 문제가 발생할 수 있음을 보여주는 매우 좋은 예입니다.

Stack Overflow Error 처리 방법

  1. 가장 간단한 해결책은 스택트레이스를 주의 깊게 검사하여 회선번호의 반복 패턴을 검출하는 것입니다.이러한 회선 번호는, 재귀적으로 호출되고 있는 코드를 나타냅니다.이러한 행을 검출하면, 코드를 주의 깊게 조사해, 재귀가 종료하지 않는 이유를 이해할 필요가 있습니다.

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

다음과 같은 기능이 있는 경우:

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

그 후 foo()는 자신을 계속 호출하고 점점 더 깊어지며, 어떤 함수를 추적하기 위해 사용된 공간이 가득 차면 스택 오버플로 오류가 발생합니다.

스택 오버플로는 스택 오버플로우를 의미합니다.보통 프로그램에는 로컬 범위 변수를 포함하고 루틴 실행이 종료될 때 반환할 위치를 지정하는 스택이 하나 있습니다.이 스택은 메모리 내 어딘가에 고정된 메모리 범위인 경향이 있기 때문에 값을 포함할 수 있는 양은 제한됩니다.

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

스택이 가득 차면 푸시할 수 없습니다.푸시하면 스택오버플로 에러가 발생합니다.

따라서 스택에 너무 많은 것을 할당하면 스택오버플로우가 표시됩니다.예를 들어, 전술한 재귀의 경우입니다.

일부 구현에서는 일부 형태의 재발을 최적화합니다.특히 꼬리 재귀입니다.테일 재귀 루틴은 재귀 콜이 루틴의 마지막 작업으로 나타나는 루틴의 한 형태입니다.이런 일상적인 통화는 단순히 점프로 바뀐다.

일부 구현에서는 재귀용으로 자체 스택을 구현하기까지 하므로 시스템의 메모리가 부족해질 때까지 재귀가 계속됩니다.

가능한 한 스택 크기를 늘리는 것이 가장 쉬운 방법입니다.그러나 그렇게 할 수 없다면 두 번째 방법은 스택 오버플로의 원인이 되는 것이 있는지 확인하는 것입니다.통화 전후를 루틴으로 인쇄하여 사용해 보십시오.이것은 실패한 루틴을 찾는 데 도움이 됩니다.

스택 오버플로는 보통 함수 호출을 너무 깊게 중첩하거나(재귀 사용 시, 즉 함수를 스스로 호출하는 경우), 힙을 사용하는 것이 더 적절한 스택에 대량의 메모리를 할당함으로써 호출됩니다.

말씀하신 대로 코드를 제시해야 합니다. :-)

스택 오버플로 오류는 보통 함수 호출이 너무 깊게 중첩되었을 때 발생합니다.이 문제의 몇 가지 예에 대해서는 Stack Overflow Code Golf 스레드를 참조해 주십시오(단, 이 질문의 경우 답변이 의도적으로 스택오버플로를 일으킵니다).

StackOverflowError에 '''로 됩니다.OutOfMemoryError매우 중요합니다.

무제한 재귀 콜은 스택스페이스를 다 쓰게 됩니다.

는 '보다 낫다'를 만들어냅니다.StackOverflowError:

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

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

StackOverflowError는, 불완전한 메모리내 콜의 합계(바이트 단위)가 스택사이즈(바이트 단위)를 넘지 않게 하기 위해서, 재귀 콜이 경계되어 있는 경우 회피할 수 있습니다.

스택 오버플로우의 가장 일반적인 원인은 지나치게 깊은 재귀 또는 무한 재귀입니다.이것이 문제라면 Java Recursion에 대한 이 튜토리얼을 통해 문제를 이해할 수 있습니다.

A StackOverflowError자바어

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)

이 경우 프로그램 변경을 통해 회피할 수 있습니다.그러나 프로그램 로직이 올바르고 그래도 문제가 해결되지 않으면 스택 크기를 늘려야 합니다.

스택에는 운영 체제에 따라 공간 제한이 있습니다.보통 크기는 8MB입니다(Ubuntu(Linux)에서는 이 제한을 확인할 수 있습니다).$ ulimit -uOS를 사용하다프로그램은 실행 시 스택을 사용하지만, 스택이 언제 사용되는지 완전히 알기 위해서는 어셈블리 언어를 확인해야 합니다.예를 들어 x86_64에서는 스택은 다음 용도로 사용됩니다.

  1. 프로시저 호출 시 반송 주소 저장
  2. 로컬 변수 저장
  3. 나중에 복원하려면 특수 레지스터 저장
  4. 인수를 프로시저 호출에 전달합니다(6개 이상).
  5. 기타: 랜덤 미사용 스택베이스, 카나리 값, 패딩 등

x86_64(일반 케이스)를 모르는 경우, 사용하는 특정 고급 프로그래밍 언어가 이러한 작업에 컴파일되는 시점만 알면 됩니다.예를 들어 C:

  • (1) → 함수 호출
  • (2) → 함수 호출의 로컬 변수(메인 포함)
  • (3) → 함수 호출의 로컬 변수 (주요 변수가 아님)
  • (4) → 함수 호출
  • (5) → 일반적으로 함수 호출이며, 일반적으로 스택 오버플로와는 관련이 없습니다.

따라서 C에서는 로컬 변수와 함수 호출만 스택을 사용합니다.스택 오버플로를 만드는 방법에는 다음 두 가지가 있습니다.

  • 큰 를 선언하는 ( 「」 「」 「」 「」 「」 「」).int array[10000][10000];)
  • 매우 깊은 재귀 또는 무한 재귀(동시에 너무 많은 함수 호출)

를 피하기 위해StackOverflowError다음을 수행할 수 있습니다.

  • 로컬 변수가 너무 큰지 확인합니다(1MB의 순서). → 힙(하이프/콜록 콜) 또는 글로벌 변수를 사용합니다.

  • 무한 재귀 확인 → 어떻게 해야 하는지 알고 있습니다...바로 잡아!

  • 너무 깊은 정상 재귀 확인 → 가장 쉬운 방법은 구현을 반복적으로 변경하는 것입니다.

또한 전역 변수, 라이브러리 포함 등...스택을 사용하지 마십시오.

상기가 기능하지 않는 경우에만 특정 OS에서 스택사이즈를 최대로 변경해 주세요.Ubuntu의 예:ulimit -s 32768(32 MB). (스택 오버플로 에러에 대한 해결 방법은 없었습니다만, 저도 경험이 별로 없습니다.)

C에서 특별한 케이스 및/또는 표준 케이스가 아닌 케이스(예:alloc()그리고 유사)를 사용하고 있다면, 이미 무엇을 하고 있는지 정확하게 알고 있을 것입니다.

다음으로 단일 링크 리스트를 반전시키는 재귀 알고리즘의 예를 나타냅니다.노트북 (4GB 메모리, 인텔 Core i5 2.3GHz CPU 64비트 및 Windows 7 사양)에서는 이 함수는 10,000에 가까운 크기의 링크 리스트에서 Stack Overflow 오류가 발생합니다.

내 요점은 항상 시스템의 규모를 고려하여 재귀를 현명하게 사용해야 한다는 것이다.

종종 재귀는 더 잘 확장되는 반복 프로그램으로 변환될 수 있습니다. (같은 알고리즘의 반복 버전은 페이지 하단에 나와 있습니다.크기가 100만인 단일 링크 목록을 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);
}

다음 상황에서는 스택오버플로 에러가 발생합니다.

public class Example3 {

    public static void main(String[] args) {

        main(new String[1]);
    }

}

java.lang의 원인이 되는 단순한 Java 예시.잘못된 재귀 호출로 인해 StackOverflowError:

class Human {
    Human(){
        new Animal();
    }
}

class Animal extends Human {
    Animal(){
        super();
    }
}

public class Test01 {
    public static void main(String[] args) {
        new Animal();
    }
}

여기 예가 있어요.

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)

다음은 OpenJDK 7의 소스 코드입니다.

언급URL : https://stackoverflow.com/questions/214741/what-is-a-stackoverflowerror

반응형