StackOverflowErrorとは何ですか?


439

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


Javaのスタックサイズが小さい。多くの再帰呼び出しなど、この問題に直面することがあります。ループによってコードを再設計できます。次のURLで、一般的なデザインパターンを見つけることができます。jndanial.com
JNDanial

それを取得する明白でない方法の1つ:new Object() {{getClass().newInstance();}};静的なコンテキスト(mainメソッドなど)に行を追加します。インスタンスコンテキストからは機能しません(スローのみInstantiationException)。
ジョン・マクレーン2018

回答:


408

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

プロセスにはヒープもあり、ヒープはプロセスの最下部にあります。メモリを割り当てると、このヒープはアドレス空間の上端に向かって大きくなる可能性があります。ご覧のとおり、ヒープがスタックと「衝突」する可能性があります(構造プレートと少し似ています!!!)。

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

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

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

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


1
元のポスター:ねえ、これは素晴らしいです。それで、再帰は常にスタックオーバーフローの原因ですか?または、他のものが同様にそれらに責任がありますか?残念ながら私はライブラリを使用していますが、理解できるライブラリは使用していません。
ジギー

4
ハッハッハなので、ここにあります:while(points <100){addMouseListeners(); moveball(); checkforcollision(); マウスリスナーがいっぱいになることに気づかなかったので、どうして私は落ち着かないと思いますか。
Ziggy、

4
いいえ、en.wikipedia.org / wiki / Stack_overflowでWikipediaの記事を参照すると、スタックオーバーフローは、変数が大きすぎてスタックに割り当てることができない場合にも発生します。
JBキング

8
スタックオーバーフローエラーを「処理」することはほとんど不可能であることを指摘しておく必要があります。ほとんどの環境では、エラーを処理するためにスタック上でコードを実行する必要があります。これは、スタックスペースがなくなると困難です。
Hot Licks、2014年

3
@JBキング:プリミティブ型と参照のみがスタックに保持されるJavaには実際には適用されません。大きなもの(配列とオブジェクト)はすべてヒープ上にあります。
jcsahnwaldtは、GoFundMonicaを2015年

107

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

ローカル変数はスタックに格納されますここに画像の説明を入力してください

画像を見ると、物事がどのように機能しているかを理解できるはずです。

関数呼び出しが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とその増加するフォロワーからパラメーターを渡しているため、再帰が終了することはありません。

-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. 再帰が正しく実装されていることを確認したら、より多くの呼び出しを許可するために、スタックのサイズを増やすことができます。インストールされているJava仮想マシン(JVM)に応じて、デフォルトのスレッドスタックサイズは512KBまたは1MBになります。-Xssフラグを使用してスレッドスタックサイズを増やすことができます。このフラグは、プロジェクトの構成またはコマンドラインを使用して指定できます。-Xss引数の形式 は次のとおりです。 -Xss<size>[g|G|m|M|k|K]


-Xss引数が新しいスレッドでのみ有効になるウィンドウを使用する場合、一部のJavaバージョンにバグがあるようです
goerlibe

65

次のような機能がある場合:

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

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


12
違う。関数は末尾再帰です。ほとんどのコンパイル済み言語には、末尾再帰の最適化があります。これは、再帰が単純なループに減少し、一部のシステムではこのコードでスタックオーバーフローが発生しないことを意味します。
Cheery

奇妙なことですが、非再帰言語は末尾再帰をサポートしていますか?
horseyguy 2009

@banisterとjavascriptの一部の実装
Pacerier 2012年

@horseyguy Scalaは、Tail再帰をサポートしています。
Ajit K'sagar 2017年

これは、スタックオーバーフローを引き起こす可能性のあるものの本質を捉えています。いいね。
Pixel

24

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

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

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

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

いくつかの実装は、いくつかの形式の再帰を最適化します。特にテール再帰。末尾再帰ルーチンはルーチンの形式であり、再帰呼び出しは、ルーチンが行う最後の処理として表示されます。そのようなルーチンの呼び出しは、ジャンプに単純化されます。

一部の実装では、再帰のために独自のスタックを実装するまで進んでいるため、システムがメモリ不足になるまで再帰を継続できます。

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


4
スタックアンダーフローのようなものはありますか?
パセリエ2012年

5
アセンブリではスタックアンダーフローが発生する可能性があります(プッシュした以上のポップ)。よくわかりませんが、負のサイズを「サポート」するCのalloca()の実装を見つけることができるかもしれません。
Score_Under

2
スタックオーバーフローとは、スタックオーバーフローという意味です。通常、ローカルスコープ変数を含むプログラム内の1つのスタックがあります-いいえ>、すべてのスレッドが..ローカル変数が含まれているすべてのメソッド呼び出しのスタックフレームを含む独自のスタックを持っている
Koray Tugay

9

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


1
おっと、Javaタグが表示されませんでした
Greg

また、ここの元のポスターから:入れ子は何に深く機能しますか?その他の機能?そして、どのようにしてスタックまたはヒープにメモリを割り当てるのですか(承知しているので、私はこれらのことの1つを知らずに明確に実行しました)。
Ziggy、

@Ziggy:はい、ある関数が別の関数を呼び出し、さらに別の関数を呼び出すなどの場合、多くのレベルの後、プログラムでスタックオーバーフローが発生します。[続き]
Chris Jester-Young

[...続き] Javaでは、スタックから直接メモリを割り当てることはできません(Cでは可能であり、これを監視する必要があります)。そのため、原因となる可能性はほとんどありません。Javaでは、「new」を使用して、すべての直接割り当てがヒープから行われます。
Chris Jester-Young

@ ChrisJester-Youngメソッドに100個のローカル変数がある場合、例外なくすべてがスタックに配置されるというのは本当ではないでしょうか。
パセリエ2012年

7

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

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


1
完全にコードを追加したいのですが、スタックオーバーフローの原因がわからないので、どのコードを追加すればよいかわかりません。すべてのコードを追加するのは不十分でしょう。
Ziggy、

あなたのプロジェクトはオープンソースですか?その場合は、Sourceforgeまたはgithubアカウントを作成し、そこにすべてのコードをアップロードしてください。:-)
Chris Jester-Young

これは素晴らしいアイデアのように聞こえますが、私は何をアップロードする必要があるかさえわからないほど初心者です。同様に、私が拡張しているクラスをインポートしているライブラリなどは、すべて私にとって未知のものです。ああ男:悪い時代。
Ziggy、


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エラーに遭遇します。

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

JVMでは、実際のラップトップの仕様は問題ではないと思います。
ケビン2013年

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.