StackOverflowError는 무엇입니까?


439

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


자바의 스택 크기는 작습니다. 그리고 많은 재귀 호출과 같은 때로는이 문제에 직면합니다. 루프별로 코드를 다시 디자인 할 수 있습니다. 이 URL에서 일반적인 디자인 패턴을 찾을 수 있습니다. jndanial.com/73
JNDanial

그것을 얻는 명백한 방법 중 하나 : new Object() {{getClass().newInstance();}};정적 컨텍스트 (예 : main메소드)에 줄 을 추가하십시오 . 인스턴스 컨텍스트에서 작동하지 않습니다 (throws only InstantiationException).
John McClane

답변:


408

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

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

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

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

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

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


1
독창적 인 포스터 : 이거 훌륭합니다. 그렇다면 재귀는 항상 스택 오버플로를 담당합니까? 아니면 다른 것들도 그들에게 책임이 있습니까? 불행히도 나는 도서관을 사용하고 있지만 이해하지는 않습니다.
Ziggy

4
하하하 여기에있다 : while (points <100) {addMouseListeners (); moveball (); checkforcollision (); pause (speed);} 와우, 마우스 리스너가 가득 찼다는 사실을 깨닫지 못한 것에 대해 멍청한 생각이 듭니다 ... 고마워요!
Ziggy

4
아니요, en.wikipedia.org/wiki/Stack_overflow 에서 Wikipedia 기사를 조회하면 스택 오버플로가 너무 커서 변수가 스택에 할당 할 수 없습니다 .
JB King

8
스택 오버플로 오류를 "처리"하는 것은 거의 불가능하다는 점을 지적해야합니다 . 대부분의 환경에서 오류를 처리하려면 스택에서 코드를 실행해야합니다. 스택 공간이 더 이상 없으면 어렵습니다.
핫 릭

3
@JB King : 기본 유형과 참조 만 스택에 유지되는 Java에는 실제로 적용되지 않습니다. 모든 큰 물건 (배열 및 객체)이 힙에 있습니다.
jcsahnwaldt는 GoFundMonica가

107

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

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

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

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]


-Xss 인수는 새로운 스레드에 적용됩니다 창을 사용하는 경우 일부 자바 버전에서 버그가 보인다
goerlibe을

65

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

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

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


12
잘못된. 함수는 꼬리 재귀입니다. 대부분의 컴파일 된 언어에는 꼬리 재귀 최적화가 있습니다. 이것은 재귀가 간단한 루프로 줄어들고 일부 시스템에서는이 코드 조각으로 스택 오버플로가 발생하지 않음을 의미합니다.
Cheery

어떤 비 기능 언어가 꼬리 재귀를 지원합니까?
horseyguy

@banister와 자바 스크립트의 일부 구현
Pacerier

@horseyguy Scala는 Tail 재귀를 지원합니다.
Ajit K'sagar

스택 오버플로를 생성 할 수있는 요소의 본질을 파악합니다. 좋은.
Pixel

24

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

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

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

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

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

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

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


4
스택 언더 플로 와 같은 것이 있습니까?
Pacerier

5
컴파일 된 언어에서는 거의 불가능하지만 어셈블리에서 스택 언더 플로우가 가능합니다 (밀어 넣은 것보다 더 많은 팝핑). 확실하지 않습니다. 음수 크기를 "지원하는"C의 alloca () 구현을 찾을 수 있습니다.
Score_Under

2
스택 오버플로는 정확히 다음을 의미합니다. 스택 오버플로. 일반적으로 프로그램에는 로컬 범위 변수를 포함하는 하나의 스택이 있습니다.-> 아니오, 모든 스레드에는 로컬 변수를 포함하는 모든 메소드 호출에 대한 스택 프레임을 포함하는 자체 스택이 있습니다.
Koray Tugay

9

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


1
죄송합니다. Java 태그를 보지 못했습니다
Greg

또한 원래 포스터에서 : 중첩 기능이 너무 깊습니까? 다른 기능? 그리고 : 어떻게 스택이나 힙에 메모리를 할당합니까? (알다시피, 나는 분명히 이들 중 하나를 수행했기 때문에).
Ziggy

@Ziggy : 그렇습니다. 한 함수가 다른 함수를 호출하고 다른 함수를 호출하는 등 여러 단계를 수행하면 프로그램에 스택 오버플로가 발생합니다. [계속]
Chris Jester-Young

[... 계속] Java에서는 스택에서 메모리를 직접 할당 할 수 없으며 (C에서는 볼 수 있고 볼 수있는 것임) 원인이 될 가능성이 적습니다. Java에서 모든 직접 할당은 "new"를 사용하여 힙에서 가져옵니다.
Chris Jester-Young

@ ChrisJester-Young 메소드에 100 개의 로컬 변수가 있으면 예외없이 스택에 모두 들어가는 것이 사실이 아닙니까?
Pacerier

7

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

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


1
코드를 완전히 추가하고 싶지만 스택 오버플로의 원인을 알지 못하므로 어떤 코드를 추가 해야할지 잘 모르겠습니다. 모든 코드를 추가하면 절름발이입니다.
Ziggy

프로젝트가 오픈 소스입니까? 그렇다면 Sourceforge 또는 github 계정을 만들고 모든 코드를 업로드하십시오. :-)
Chris Jester-Young

이것은 좋은 생각처럼 들리지만, 내가 업로드해야 할 것을조차 알지 못하는 멍청한 놈입니다. 마찬가지로, 내가 확장하는 클래스를 가져 오는 라이브러리는 ... 나에게 알려지지 않았습니다. 오 남자 : 나쁜 시간.
Ziggy

5

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


5

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

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

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

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

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

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


3

다음은 단일 연결 목록을 되 돌리는 재귀 알고리즘의 예입니다. 다음 사양 (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);
}

JVM에서는 실제로 랩톱의 사양이 중요하지 않다고 생각합니다.
kevin

3

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)

위의 경우 프로그래밍 방식으로 변경하면 피할 수 있습니다. 그러나 프로그램 논리가 정확하고 여전히 발생하면 스택 크기를 늘려야합니다.


0

이것은 전형적인 경우입니다 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 의 소스 코드는 다음과 같습니다.


0

위기 상황에서 아래 상황에서는 스택 오버플로 오류가 발생합니다.

public class Example3 {

public static void main(String[] args) {

    main(new String[1]);

}

}


-1

여기에 예가 있습니다

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) 전화를 걸고 다시 전화를 걸 것입니다.


-1

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

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.