1. ホーム
  2. java

[解決済み] Java のスタックサイズを大きくするには?

2022-06-04 14:15:38

質問

私はこの質問を、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

また、回答の中で指摘されているのが -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 関数は65以上の数(実際には20以上でも)の正確な階乗を計算することができません。これはJavaの組み込み型である long はオーバーフローしてしまうからです。リファクタリング fact を返すように BigInteger ではなく long のようにすれば、大きな入力に対しても正確な結果が得られます。

どのように解決するのか?

ふむ...私の場合、999MB 未満のスタックで動作します。

> java -Xss4m Test
0

(Windows JDK 7、ビルド 17.0-b05 クライアント VM、および Linux JDK 6 - 投稿されたものと同じバージョン情報)