web-dev-qa-db-ja.com

StackOverflowErrorとは何ですか?

StackOverflowErrorとは何ですか、その原因は何ですか。また、それらにどのように対処する必要がありますか?

391
Ziggy

パラメータとローカル変数は stack に割り当てられます(参照型では、オブジェクトは heap に存在し、スタック内の変数はヒープ上のそのオブジェクトを参照します)。スタックは通常、アドレス空間の upper の末尾にあり、使い切られると、アドレス空間の bottom に向かって(つまりゼロに向かって)向かいます。

あなたのプロセスには heap もあり、これはあなたのプロセスの bottom の最後にあります。メモリを割り当てると、このヒープはアドレス空間の上限に向かって大きくなります。ご覧のとおり、スタックと "collide" の間にヒープが発生する可能性があります(テクトニックプレートのようなものです!!!)。

スタックオーバーフローの一般的な原因は、 不正な再帰呼び出し です。通常、これは再帰関数が正しい終了条件を持っていないために発生します。したがって、永久に自分自身を呼び出すことになります。

しかし、GUIプログラミングでは、 間接再帰 を生成することは可能です。たとえば、アプリがPaintメッセージを処理しているときに、それらを処理している間に、システムが別のPaintメッセージを送信するようにする関数を呼び出すことがあります。ここでは明示的に自分自身と呼ばれることはありませんが、OS/VMがあなたのためにそれをしました。

それらに対処するには、コードを調べる必要があります。自分自身を呼び出す関数がある場合は、終了条件があることを確認してください。もしそうであれば、関数を呼び出すときに引数の1つを少なくとも修正していることを確認してください。

明白な再帰関数がない場合は、 間接的に のようなライブラリ関数を呼び出しているかどうかを確認してください(上記の暗黙の場合のように)。

361
Sean

これを説明するために、まずlocal変数とオブジェクトがどのように格納されるかを理解しましょう。

ローカル変数はstackに格納されています。 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とその増加するフォロワーからパラメータを渡したため、再帰は終了しません。

スレッドスタックのサイズを1MBに指定する-Xss1Mフラグを使用した実行例を以下に示します。

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)に応じて、デフォルトのスレッドスタックサイズは512KB、または1MBに等しくなります。 -Xssフラグを使ってスレッドスタックサイズを増やすことができます。このフラグは、プロジェクトの設定またはコマンドラインから指定できます。 -Xss引数のフォーマットは、次のとおりです。-Xss<size>[g|G|m|M|k|K]

86
Varun

次のような機能があるとします。

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

そうするとfoo()はそれ自身を呼び出し続け、ますます深くなります。そして、自分がどの関数に入っているかを追跡するために使用されるスペースがいっぱいになると、スタックオーバーフローエラーが発生します。

62
Khoth

スタックオーバーフローとは、スタックオーバーフローのことです。通常、プログラム内に1つのスタックがあり、ローカルスコープの変数と、ルーチンの実行が終了したときに返す場所を指定します。そのスタックはメモリ内のどこかに固定されたメモリ範囲になる傾向があるため、値を格納できる量は制限されています。

スタックが空の場合はポップできません。空の場合はスタックアンダーフローエラーが発生します。

スタックがいっぱいの場合はプッシュできません。そうするとスタックオーバーフローエラーになります。

そのため、スタックオーバーフローは、あなたがスタックに割り当て過ぎたところに現れます。たとえば、前述の再帰では。

いくつかの実装はいくつかの形の再帰を最適化します。特に尾の再帰末尾再帰ルーチンは、再帰呼び出しがそのルーチンが行うことの最終的なものとして現れるルーチンの形式です。このような日常的な呼び出しは単純にジャンプして減少します。

いくつかの実装は再帰のためにそれら自身のスタックを実装する限りまで行きます、それ故にそれらはシステムがメモリを使い果たすまで再帰が続くことを可能にします。

あなたが試すことができる最も簡単なことはあなたができるならあなたのスタックサイズを増やすことでしょう。それができない場合は、2番目に良いのは明らかにスタックオーバーフローを引き起こす何かがあるかどうかを調べることです。ルーチン呼び出しの前後に何かを印刷してみてください。これにより、失敗したルーチンを見つけることができます。

23
Cheery

スタックオーバーフローは、通常、関数呼び出しを深く入れ子にすること(再帰を使用する場合は特に簡単)、つまりヒープを使用する方が適切な場合はスタックに大量のメモリを割り当てることによって呼び出されます。

8
Greg

あなたが言うように、あなたはいくつかのコードを見せる必要があります。 :-)

スタックオーバーフローエラーは、通常、関数呼び出しが深すぎる場合に発生します。これがどのように起こるかのいくつかの例については、 スタックオーバーフローコードGolf スレッドを参照してください(ただし、この質問の場合、答えは意図的にスタックオーバーフローを引き起こします)。

6

StackOverflowErrorはヒープに対するのと同様に、OutOfMemoryErrorはスタックに対するものです。

無制限の再帰呼び出しはスタックスペースを使い果たします。

次の例ではStackOverflowErrorが生成されます。

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

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

不完全なインメモリ呼び出し(バイト単位)の合計がスタックサイズ(バイト単位)を超えないように再帰呼び出しが制限されている場合、StackOverflowErrorは使用できません。

5
Vikram

スタックオーバーフローの最も一般的な原因は、 過度に深い、または無限再帰です です。これがあなたの問題であるならば、 このJava再帰についてのチュートリアル は問題を理解するのを助けるかもしれません。

5
splattne

StackOverflowErrorはJavaの実行時エラーです。

JVMによって割り当てられた呼び出しスタックメモリの量を超えたときにスローされます。

呼び出しスタックが超過したときにStackOverflowErrorbeingがスローされる一般的なケースは、過度の深度または無限再帰のためです。

例:

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)

上記の場合、プログラムによる変更を避けることができます。しかし、プログラムロジックが正しく、それでも発生する場合は、スタックサイズを増やす必要があります。

3
Rahul Sah

これは片方向リンクリストを逆にするための再帰的アルゴリズムの例です。以下の仕様(4Gメモリ、Intel Core i5 2.3GHz CPU、64ビットWindows 7)を搭載したラップトップでは、この関数は10,000に近いサイズのリンクリストでStackOverflowエラーになります。

私は、常にシステムの規模を考慮して、再帰を慎重に使用する必要があります。多くの場合、再帰は反復プログラムに変換できます。 (同じアルゴリズムの繰り返しバージョンがページの下部に表示されています。これは、9ミリ秒で100万サイズの単一リンクリストを逆にします。)

    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);
}
3
Yiling

これが例です

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)は自分自身を呼び出してから、もう一度自分自身を呼び出します。

0
John S.

「スタックオーバーラン(オーバーフロー)」という用語はよく使用されますが、誤称です。攻撃はスタックをオーバーフローさせるのではなく、スタックにバッファします。

- 講義のスライドから Dr. Dieter Gollmann

0
Sergiu

これは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();
        }
    }

ジャワ

    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のStackOverflowErrorのソースコードです

0
user8389458