この質問をして、JVMのランタイムコールスタックサイズを増やす方法を知りました。私はこれに対する答えを得ました。また、Javaが大規模なランタイムスタックが必要とされる状況をどのように処理するかに関連する多くの有用な答えとコメントも得ました。質問の回答をまとめました。
もともとは、JVMスタックサイズを増やして、プログラムをなしで実行するようなプログラムにしたかったのStackOverflowError
です。
public class TT {
public static long fact(int n) {
return n < 2 ? 1 : n * fact(n - 1);
}
public static void main(String[] args) {
System.out.println(fact(1 << 15));
}
}
対応する構成設定は、java -Xss...
十分に大きな値を持つコマンドラインフラグです。TT
上記のプログラムの場合、OpenJDKのJVMでは次のように機能します。
$ javac TT.java
$ java -Xss4m TT
回答の1つは、-X...
フラグは実装に依存することも指摘しています。使っていた
java version "1.6.0_18"
OpenJDK Runtime Environment (IcedTea6 1.8.1) (6b18-1.8.1-0ubuntu1~8.04.3)
OpenJDK 64-Bit Server VM (build 16.0-b13, mixed mode)
1つのスレッドに対してのみ大きなスタックを指定することもできます(回答の1つで方法を参照してください)。これをjava -Xss...
必要としないスレッドのメモリを浪費しないようにするために、これをお勧めします。
上記のプログラムが正確に必要とするスタックの大きさが気になったので、n
増やして実行しました。
- -Xss4mは、
fact(1 << 15)
- -Xss5mは、
fact(1 << 17)
- -Xss7mは、
fact(1 << 18)
- -Xss9mは、
fact(1 << 19)
- -Xss18mで十分な場合があります
fact(1 << 20)
- -Xss35mは、
fact(1 << 21)
- -Xss68mは、
fact(1 << 22)
- -Xss129mは、
fact(1 << 23)
- -Xss258mは、
fact(1 << 24)
- -Xss515mで十分です
fact(1 << 25)
上記の数値から、Javaは上記の関数でスタックフレームあたり約16バイトを使用しているようです。これは妥当です。
含まれている上記の列挙は十分に可能の代わりに十分であるスタック要件は確定的ではないので、:実行していることを複数回同じソースファイルで、同じ-Xss...
時々は成功し、時には生み出しますStackOverflowError
。たとえば、1 << 20の場合、-Xss18m
10のうち7ランで十分であり、-Xss19m
常に十分であるとは限りません-Xss20m
でした(100のうちすべての100ランで)。ガベージコレクション、JITが起動するなど、この非決定的な動作が発生しますか?
で出力されたスタックトレースStackOverflowError
(およびその他の例外)には、ランタイムスタックの最新の1024要素のみが表示されます。以下の回答は、到達した正確な深度をカウントする方法を示しています(1024よりもはるかに大きい場合があります)。
回答した多くの人々は、同じアルゴリズムのスタックをあまり必要としない代替実装を検討することが、適切で安全なコーディング手法であると指摘しています。一般に、一連の再帰関数を反復関数に変換することができます(Stack
ランタイムスタックではなくヒープに入力される、たとえばオブジェクトを使用)。この特定のfact
関数の場合、変換は非常に簡単です。私の反復バージョンは次のようになります:
public class TTIterative {
public static long fact(int n) {
if (n < 2) return 1;
if (n > 65) return 0; // Enough powers of 2 in the product to make it (long)0.
long f = 2;
for (int i = 3; i <= n; ++i) {
f *= i;
}
return f;
}
public static void main(String[] args) {
System.out.println(fact(1 << 15));
}
}
参考までに、上記の反復解が示すようにfact
、Java組み込み型long
がオーバーフローするため、関数は65を超える数値(実際には20を超える数値)の正確な階乗を計算できません。大規模な入力でも正確な結果が得られるのではなく、fact
を返すようにリファクタリングします。BigInteger
long