質問

とは何ですか StackOverflowError, 、何が原因で、どう対処すればいいのでしょうか?

役に立ちましたか?

解決

パラメーターとローカル変数はスタックに割り当てられます(参照タイプでは、オブジェクトはヒープに存在し、スタック内の変数はヒープ上のオブジェクトを参照します) 。スタックは通常、アドレス空間の上部端にあり、使用されるとアドレス空間の下部に向かって(つまりゼロに向かって)なります。

プロセスにはヒープもあります。これは、プロセスのの終わりにあります。メモリを割り当てると、このヒープはアドレス空間の上限に向かって大きくなる可能性があります。ご覧のとおり、ヒープがスタックと <!> quot; collide <!> quot; する可能性があります(構造プレートに少し似ています!!!)。

スタックオーバーフローの一般的な原因は、不正な再帰呼び出しです。通常、これは、再帰関数に正しい終了条件がない場合に発生するため、永久に自分自身を呼び出すことになります。または、終了条件に問題がない場合、それを実行する前に再帰呼び出しが多すぎることが原因である可能性があります。

ただし、GUIプログラミングでは、間接再帰を生成できます。たとえば、アプリがペイントメッセージを処理している場合、それらの処理中に、システムが別のペイントメッセージを送信する関数を呼び出す場合があります。ここでは、明示的に自分自身を呼び出したわけではありませんが、OS / VMが自動的に呼び出しています。

それらに対処するには、コードを調べる必要があります。自分自身を呼び出す関数がある場合は、終了条件があることを確認してください。持っている場合は、関数を呼び出すときに少なくとも引数の1つが変更されていることを確認してください。変更しないと、再帰的に呼び出される関数に目に見える変化はなく、終了条件は役に立ちません。また、有効な終了条件に達する前にスタックスペースがメモリ不足になる可能性があるため、メソッドがより再帰的な呼び出しを必要とする入力値を処理できることを確認してください。

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

他のヒント

これを説明するには、まず、ローカル変数とオブジェクトがどのように保存されているかを理解しましょう。

ローカル変数は stack に保存されます: ここに画像の説明を入力

画像を見れば、物事の仕組みを理解できるはずです。

関数呼び出しがJavaアプリケーションによって呼び出されると、スタックフレームが呼び出しスタックに割り当てられます。スタックフレームには、呼び出されたメソッドのパラメーター、そのローカルパラメーター、およびメソッドの戻りアドレスが含まれます。戻りアドレスは、呼び出されたメソッドが戻った後、プログラムの実行を継続する実行ポイントを示します。新しいスタックフレーム用のスペースがない場合、StackOverflowErrorはJava仮想マシン(JVM)によってスローされます。

Javaアプリケーションを使い果たす可能性のある最も一般的なケースは、再帰です。再帰では、メソッドは実行中に自身を呼び出します。再帰は強力な汎用プログラミング手法と見なされますが、recursivePrintを避けるために注意して使用する必要があります。

0をスローする例を以下に示します。

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

この例では、整数を出力し、引数として次に続く整数を使用して、整数を出力する-Xss1Mという再帰的メソッドを定義します。 -Xssをパラメーターとして渡すまで、再帰は終了します。ただし、この例では、1とその増加するフォロワーからパラメーターを渡したため、再帰は終了しません。

スレッドスタックのサイズを1MBに指定する-Xss<size>[g|G|m|M|k|K]フラグを使用したサンプル実行を以下に示します。

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 <!>#8217;の初期構成に応じて、結果は異なる場合がありますが、最終的には<=>がスローされます。この例は、慎重に実装しないと、再帰が問題を引き起こす可能性のある非常に良い例です。

StackOverflowErrorの対処方法

  1. 最も簡単な解決策は、スタックトレースを慎重に検査し、 行番号の繰り返しパターンを検出します。これらの行番号 再帰的に呼び出されるコードを示します。これらを検出したら 行、コードを慎重に検査し、なぜ 再帰は終了しません。

  2. 再帰が検証された場合     が正しく実装されている場合は、スタックのサイズを増やすことができます<!>#8217;     より多くの呼び出しを許可するため。 Javaに依存     仮想マシン(JVM)がインストールされている場合、デフォルトのスレッドスタックサイズは      512KB、または1MB のいずれかに等しい。スレッドスタックを増やすことができます     <=>フラグを使用したサイズ。このフラグは、     project <!>#8217; sの構成、またはコマンドライン経由。の形式     <=>引数は次のとおりです。     <=>

次のような関数がある場合:

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

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

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

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

スタックがいっぱいの場合はプッシュできません。プッシュするとスタックオーバーフローエラーが発生します。

したがって、スタックへの割り当てが多すぎると、スタックオーバーフローが発生します。たとえば、前述の再帰では。

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

一部の実装では、再帰のために独自のスタックを実装するため、システムがメモリを使い果たすまで再帰を続行できます。

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

スタックオーバーフローは通常、関数呼び出しのネストが深すぎる(特に再帰を使用する場合、つまりそれ自体を呼び出す関数の場合)か、ヒープを使用する方が適切なスタックに大量のメモリを割り当てることで呼び出されます。

言うように、コードを表示する必要があります。 :-)

通常、関数がネストを深く呼び出しすぎると、スタックオーバーフローエラーが発生します。これがどのように起こるかの例については、 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.3GHz CPU、64ビットWindows 7)を搭載したラップトップでは、10,000に近いサイズのリンクリストのStackOverflowエラーが発生します。

私のポイントは、システムの規模を常に考慮して、再帰を慎重に使用する必要があるということです。 多くの場合、再帰は反復プログラムに変換できます。これはより優れた拡張性を備えています。 (同じアルゴリズムの1つの反復バージョンがページの下部に示されており、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);
}

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)

こちらOpenJDK 7のStackOverflowErrorのソースコードです

「スタック オーバーラン (オーバーフロー)」という用語がよく使用されますが、これは誤った呼び名です。攻撃はスタックをオーバーフローさせるのではなく、スタック上にバッファーを置きます。

-- の講演スライドより 教授博士。ディーター・ゴルマン

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top