web-dev-qa-db-ja.com

実際に何がスタックオーバーフローエラーを引き起こしますか?

私は至る所で見ました、そして、確かな答えを見つけることができません。ドキュメントによると、Javaは次の状況で Java.lang.StackOverflowError エラーをスローします。

アプリケーションの再帰が深すぎるためにスタックオーバーフローが発生したときにスローされます。

しかしこれは二つの疑問を投げかける:

  • 再帰だけではなく、スタックオーバーフローが発生する他の方法はありませんか。
  • StackOverflowErrorは、JVMが実際にスタックをオーバーフローする前に発生するのか、それとも後に発生するのですか。

2番目の質問について詳しく説明します。

JavaがStackOverflowErrorを投げたとき、あなたは安全にスタックがヒープに書き込まなかったと仮定することができますか?スタックオーバーフローを発生させる関数のtry/catchでスタックまたはヒープのサイズを縮小した場合、作業を続けることができますか?これはどこかに文書化されていますか?

私は探していません。

  • StackOverflowは悪い再帰のために起こります。
  • StackOverflowは、ヒープがスタックを満たすときに発生します。
234
retrohacker

バッファに割り当てられていないメモリに書き込む危険性があるため、 stackoverflow error がネイティブプログラムのバッファオーバーフロー例外のようなものであると考えているようです。他のメモリ位置そうではありません。

JVMには各スレッドの各スタックに割り当てられたメモリがあり、メソッドを呼び出そうとしてこのメ​​モリがいっぱいになると、JVMはエラーをスローします。長さNの配列のインデックスNに書き込もうとした場合と同じです。メモリの破損は発生しません。スタックはヒープに書き込めません。

StackOverflowErrorは、OutOfMemoryErrorがヒープに対して何をするのかをスタックに伝えます。それは単に、利用可能なメモリがもうないことを示すものです。

仮想マシンエラー(6.3)からの説明

StackOverflowError:通常、結果としてスレッドが無限の再帰呼び出しを行っているため、Java仮想マシンの実装でスレッドのスタックスペースが不足しています。実行中のプログラムの障害.

195
JB Nizet

再帰だけではなく、スタックオーバーフローが発生する他の方法はありませんか。

もちろんです。決して戻ることなく、メソッドを呼び出し続けるだけです。ただし、再帰を許可しない限り、たくさんのメソッドが必要になります。実際、違いはありません。スタックフレームは、再帰的な方法であるかどうかにかかわらず、スタックフレームです。

2番目の質問に対する答えは、次のとおりです。スタックオーバーフローは、JVMが次の呼び出しのためにスタックフレームを割り当てようとしたときに検出され、それが不可能であることが判明したときです。だから、何も上書きされません。

52
Ingo

再帰だけではなく、スタックオーバーフローが発生する他の方法はありませんか。

チャレンジが承認されました:) StackOverflowError 再帰なし (挑戦は失敗しました、コメントを見てください):

public class Test
{
    final static int CALLS = 710;

    public static void main(String[] args)
    {
        final Functor[] functors = new Functor[CALLS];
        for (int i = 0; i < CALLS; i++)
        {
            final int finalInt = i;
            functors[i] = new Functor()
            {
                @Override
                public void fun()
                {
                    System.out.print(finalInt + " ");
                    if (finalInt != CALLS - 1)
                    {
                        functors[finalInt + 1].fun();
                    }
                }
            };
        }
        // Let's get ready to ruuuuuuumble!
        functors[0].fun(); // Sorry, couldn't resist to not comment in such moment. 
    }

    interface Functor
    {
        void fun();
    }
}

標準のjavac Test.Javaでコンパイルし、Java -Xss104k Test 2> outで実行します。その後、more outはあなたに言うでしょう:

Exception in thread "main" Java.lang.StackOverflowError

もう一度試してください。

これで、アイデアはさらに簡単になりました。 Javaのプリミティブはスタックに格納できます。それでは、double a1,a2,a3...のように、たくさんのdoubleを宣言しましょう。このスクリプトは コード を書き、コンパイルして実行することができます。

#!/bin/sh

VARIABLES=4000
NAME=Test
FILE=$NAME.Java
SOURCE="public class $NAME{public static void main(String[] args){double "
for i in $(seq 1 $VARIABLES);
do
    SOURCE=$SOURCE"a$i,"
done
SOURCE=$SOURCE"b=0;System.out.println(b);}}"
echo $SOURCE > $FILE
javac $FILE
Java -Xss104k $NAME

そして...私は予想外のことになりました:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007f4822f9d501, pid=4988, tid=139947823249152
#
# JRE version: 6.0_27-b27
# Java VM: OpenJDK 64-Bit Server VM (20.0-b12 mixed mode linux-AMD64 compressed oops)
# Derivative: IcedTea6 1.12.6
# Distribution: Ubuntu 10.04.1 LTS, package 6b27-1.12.6-1ubuntu0.10.04.2
# Problematic frame:
# V  [libjvm.so+0x4ce501]  JavaThread::last_frame()+0xa1
#
# An error report file with more information is saved as:
# /home/adam/Desktop/test/hs_err_pid4988.log
#
# If you would like to submit a bug report, please include
# instructions how to reproduce the bug and visit:
#   https://bugs.launchpad.net/ubuntu/+source/openjdk-6/
#
Aborted

100%繰り返しです。これはあなたの2番目の質問に関連しています。

StackOverflowErrorは、JVMが実際にスタックをオーバーフローする前に発生するのか、それとも後に発生するのですか。

したがって、OpenJDK 20.0-b 12の場合、JVMが最初に爆発したことがわかります。しかし、それはバグのように思えます、私はよく分からないので、誰かがコメントでそれを確認してください。これを報告すべきですか? JVM specification link (コメントの中で JB Nizet で与えられる)によれば、JVMは死ぬのではなくStackOverflowErrorをスローすべきです。

スレッド内の計算で、許可されているよりも大きいJava Virtual Machineスタックが必要な場合、Java Virtual MachineはStackOverflowErrorをスローします。


三回目。

public class Test {
    Test test = new Test();

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

新しいTestオブジェクトを作成したいです。そのため、その(暗黙の)コンストラクタが呼び出されます。しかし、その直前に、Testのすべてのメンバーが初期化されます。そのため、Test test = new Test()が最初に実行されます。

新しいTestオブジェクトを作成したいのですが….

更新:運が悪い、これは再帰です、私はそれについての質問をしました こちら

27

"StackOverFlowException"はありません。あなたが言っているのは "StackOverFlowError"です。

そうすればスタックはクリアされますが、それは悪いと醜いオプションになるだろうのでそれをキャッチすればはいあなたは働き続けることができます。

正確にエラーが発生した場合 - メソッドを呼び出したときに、JVMがそれを実行するのに十分なメモリがあるかどうかを確認する。それが不可能な場合はもちろん、エラーがスローされます。

  • いいえ、それはあなたがそのエラーを得ることができる唯一の方法です:あなたのスタックをいっぱいにすること。しかし、再帰だけではなく、他のメソッドを無限に呼び出すメソッドも呼び出します。これは非常に具体的なエラーです。
  • スタックが一杯になる前、厳密に検証したときにスローされます。利用可能なスペースがない場合、どこにデータを置きますか?他を上書きしますか?なるほど。
3
GabrielBB

StackOverFlowErrorの最も一般的な原因は、過度に深い、または無限再帰です。

例えば:

public int yourMethod(){
       yourMethod();//infinite recursion
}

Javaの場合:

メモリ内にヒープとスタックのtwo領域があります。 stack memoryはローカル変数と関数呼び出しを格納するために使用され、heap memoryはJavaでオブジェクトを格納するために使用されます。

関数呼び出しやローカル変数を格納するためのメモリがスタックに残っていない場合、JVMはJava.lang.StackOverFlowErrorをスローします。

オブジェクトを作成するためのヒープスペースがこれ以上ない場合、JVMはJava.lang.OutOfMemoryErrorをスローします。

3
Lazy Coder

物事をJavaに格納できる場所は主に2つあります。 1つ目はヒープです。これは動的に割り当てられたオブジェクトに使用されます。 new

さらに、実行中の各スレッドはそれぞれ独自のスタックを取得し、そのスタックに割り当てられた量のメモリを取得します。

メソッドを呼び出すと、メソッド呼び出し、渡されたパラメータ、割り当てられているローカル変数を記録するためにデータがスタックにプッシュされます。 5つのローカル変数と3つのパラメータを持つメソッドは、ローカル変数を持たないvoid doStuff()メソッドよりも多くのスタックスペースを使用します。

スタックの主な利点は、メモリの断片化がなく、1つのメソッド呼び出しのすべてがスタックの一番上に割り当てられ、メソッドから戻るのが簡単なことです。メソッドから戻るには、スタックを元のメソッドに戻して戻り値に必要な値を設定すれば完了です。

スタックはスレッドごとに固定サイズであるため(Java仕様は固定サイズを必要としませんが、執筆時点でのほとんどのJVM実装は固定サイズを使用します)、メソッドを作成するたびにスタック上のスペースが必要になるためうまくいけば電話をかけることが今それがなぜそれが尽きることができるのか、そして何がそれを尽きるのを引き起こすことができるのか明らかになるはずです。メソッド呼び出しの数が決まっているわけではなく、再帰について具体的なことは何もありません。メソッドを呼び出そうとすると例外が発生し、十分なメモリがありません。

もちろん、スタックのサイズは十分に大きく設定されているため、通常のコードでは起こりそうもありません。しかし再帰的なコードでは、非常に深いところまで再帰することは非常に簡単でありえます、そしてその時点であなたはこのエラーに遭遇し始めます。

3
Tim B

StackOverflowErrorは、アプリケーションの再帰が深すぎるために発生します(これは期待する答えではありません)。

StackOverflowErrorで他に起こることはStackOverflowErrorを取得するまでメソッドからメソッドを呼び出し続けることですが、誰もStackOverflowErrorを取得するようにプログラムすることはできず、そのようなプログラマが のコーディング標準に従わない場合プログラム中にすべてのプログラマーが理解しなければならない循環的な複雑さ 。 'StackOverflowError'のような理由はそれを修正するために多くの時間が必要になります。

しかし、無意識のうちにStackOverflowErrorの原因となる1行または2行をコーディングすることは理解でき、JVMはそれをスローしますので、すぐに修正できます。 ここで は他の質問に対する私の答えです。

2
AmitG

C#では、オブジェクトのプロパティを誤って定義することで、スタックオーバーフローを別の方法で実現できます。例えば ​​:

private double hours;

public double Hours
        {
            get { return Hours; }
            set { Hours = value; }
        }

ご覧のとおり、これは大文字のHを付けてHoursを永遠に返し続けます。

言語の管理者(CLR、JRE)がコードが無限ループに陥ったことを検出するため、メモリ不足やマネージ言語の使用時にもスタックオーバーフローが発生することがよくあります。

StackOverflowは、関数呼び出しが行われてスタックがいっぱいになると発生します。

ArrayOutOfBoundExceptionと同じです。それは何も破壊することはできません、実際にそれを捕まえてそこから回復することは非常に可能です。

これは通常、制御されていない再帰の結果として発生しますが、単に関数呼び出しの非常に深いスタックを持っていることによっても発生する可能性があります。

0
njzk2