Javaスタックサイズを増やす方法


123

この質問をして、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の場合、-Xss18m10のうち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を返すようにリファクタリングします。BigIntegerlong


見た目はシンプルです。fact()は32K回再帰的に呼び出されます。スタックは1MB未満である必要があります。:-/
アーロンディグラ

@Aaron:+関数オーバーヘッド、つまりLOT
ハーフダン

4
スタックの問題は別として。あなたはあなたの長い整数を爆破していることに注意してください。1 << 4は、マイナスになる前に使用できる最大値で、次に0になります。BigIntegerを使用してみてください
Sean

関数のオーバーヘッドが実際にはそれほど大きくないかどうかはわかりませんが、スタックスペースの数メガバイトのオーダーで2 ^ 15の呼び出しを行うことができるはずです。
Neil Coffey

7
注:すべてのスレッドのスタックサイズを設定し、無意味な結果を生成しています。これはすべて、コードの1行のリファクタリングを回避するためです。優先事項を整理していただき、ありがとうございます。:P
Peter Lawrey 2010

回答:


78

うーん...それは私のために動作し、999MBよりはるかに少ないスタックで動作します:

> java -Xss4m Test
0

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


1
おそらくそれは私のコメントのためだったのですが、ニールが投稿したのと同じことを知ったときに削除しました。
Sean

この質問とあなたの答えのおかげで、私はなんとか私の任務を完了することができました。私のDFS関数は、最大10 ^ 5の頂点を持つグラフで再帰する必要がありました。最後に-Xss129m:Dで動作しました
bholagabbar

11

スタックトレースの繰り返し行によって「深度1024」を計算したと思いますか?

明らかに、Throwableのスタックトレース配列の長さは1024に制限されているようです。次のプログラムを試してください。

public class Test {

    public static void main(String[] args) {

        try {
            System.out.println(fact(1 << 15));
        }
        catch (StackOverflowError e) {
            System.err.println("true recursion level was " + level);
            System.err.println("reported recursion level was " +
                               e.getStackTrace().length);
        }
    }

    private static int level = 0;
    public static long fact(int n) {
        level++;
        return n < 2 ? n : n * fact(n - 1);
    }
}

9

スレッドスタックサイズを試してみたい場合は、Hotspot JVMの-Xssオプションを確認してください。JVMへの-Xパラメータはディストリビューション固有のIIRCであるため、これはHotspot以外のVMでは何か異なる場合があります。

Hotspotではjava -Xss16M、サイズを16 MBにしたい場合にこのようになります。

タイプjava -X -helpはあなたが渡すことができ配布特定のJVMパラメータのすべてを見たい場合。これは他のJVMで同じように動作しますが、それはホットスポットの特定のパラメータのすべてを印刷した場合、私はわかりません。

価値があること-Javaでの再帰メソッドの使用を制限することをお勧めします。それらを最適化するのはあまり良いことではありません。JVMが末尾再帰をサポートしていない場合(JVMは末尾呼び出しの最適化を妨げますか?を参照)。上記の階乗コードをリファクタリングして、再帰的なメソッド呼び出しの代わりにwhileループを使用してみてください。


8

プロセス内のスタックのサイズを制御する唯一の方法は、新しいを開始することThreadです。ただし、-Xssパラメーターを使用して自己呼び出しサブJavaプロセスを作成することによって制御することもできます。

public class TT {
    private static int level = 0;

    public static long fact(int n) {
        level++;
        return n < 2 ? n : n * fact(n - 1);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(null, null, "TT", 1000000) {
            @Override
            public void run() {
                try {
                    level = 0;
                    System.out.println(fact(1 << 15));
                } catch (StackOverflowError e) {
                    System.err.println("true recursion level was " + level);
                    System.err.println("reported recursion level was "
                            + e.getStackTrace().length);
                }
            }

        };
        t.start();
        t.join();
        try {
            level = 0;
            System.out.println(fact(1 << 15));
        } catch (StackOverflowError e) {
            System.err.println("true recursion level was " + level);
            System.err.println("reported recursion level was "
                    + e.getStackTrace().length);
        }
    }

}

この有益な回答のおかげで、に加えてオプションについて知っておくと便利ですjava -Xss...
PTS

1
私はこれに興奮しましたが、その後docs.oracle.com/javase/6/docs/api/java/lang/Thread.html#Thread-スタックサイズコンストラクターを読んだので、興奮はなくなりました。
ケルログ2013

文書だけ言うときのプラットフォームは、彼らしている私の不思議- 「いくつかのプラットフォームでは、」
デニス・C

3

このオプションを追加

--driver-java-options -Xss512m

あなたのspark-submitコマンドにこの問題を修正します。


2

あなたはすべての健全なアプローチを避けたがっているので、賢明な解決策を与えることは困難です。1行のコードをリファクタリングするのが賢明なソリューションです。

注:-Xssを使用すると、すべてのスレッドのスタックサイズが設定されるため、非常に悪い考えです。

別のアプローチは、次のようにコードを変更するバイトコード操作です。

public static long fact(int n) { 
    return n < 2 ? n : n > 127 ? 0 : n * fact(n - 1); 
}

n> 127のすべての回答が0である場合、これはソースコードの変更を回避します。


1
スタックサイズを高く設定すると、それを必要としないスレッドのメモリが浪費されることを指摘していただきありがとうございます。またfact、問題の関数をリファクタリングしてスタックスペースを大幅に削減できることを指摘していただきありがとうございます。
PTS

1
@pts、ありがとうございます。これははるかに複雑なユースケースを考えると賢明な質問だと思いますが、それらは非常にまれです。
Peter Lawrey、2009

0

おかしい!あなたは1 << 15の深さの再帰を生成したいと言っています??? !!!!

私はそれを試さないことをお勧めします。スタックのサイズはになります2^15 * sizeof(stack-frame)。スタックフレームのサイズはわかりませんが、2 ^ 15は32.768です。かなり…まあ、それが1024(2 ^ 10)で止まる場合は、実際の設定の2 ^ 5倍、つまり32倍にする必要があります。


0

他のポスターは、メモリを増やす方法とあなたが電話を思い出すことができると指摘しました。多くのアプリケーションで、スターリングの公式を使用して大きなnを近似できることをお勧めします。メモリフットプリントがほとんどなく、非常に高速です。

関数とコードの分析が含まれているこの投稿を少し見てみましょう。

http://threebrothers.org/brendan/blog/stirlings-approximation-formula-clojure/


0

私がやったアナグラムexcersizeのようなものです、カウント変更問題ではなく50の000宗派(コイン)とを。私はそれが反復的に行うことができることを確認していない、私は気にしません。私は-xssオプションが効果がないことを知っています-1024スタックフレームの後で常に失敗しました(javaまたはprintStackTraceの制限にscalaがうまく機能しない可能性があります。わかりません)。とにかく説明されているように、これは悪いオプションです。アプリ内のすべてのスレッドが巨大になることを望んでいません。ただし、新しいスレッド(スタックサイズ)を使用していくつかの実験を行いました。これは確かに機能し、

  def measureStackDepth(ss: Long): Long = {
    var depth: Long = 0
      val thread: Thread = new Thread(null, new Runnable() {
        override def run() {
          try {
          def sum(n: Long): Long = {depth += 1; if (n== 0) 0 else sum(n-1) + 1}
          println("fact = " + sum(ss * 10))
          } catch {
            case e: StackOverflowError => // eat the exception, that is expected
          }
        }
      }, "deep stack for money exchange", ss)
      thread.start()
      thread.join()
    depth
  }                                               //> measureStackDepth: (ss: Long)Long


  for (ss <- (0 to 10)) println("ss = 10^" +  ss + " allows stack of size " -> measureStackDepth((scala.math.pow (10, ss)).toLong) )
                                                  //> fact = 10
                                                  //| (ss = 10^0 allows stack of size ,11)
                                                  //| fact = 100
                                                  //| (ss = 10^1 allows stack of size ,101)
                                                  //| fact = 1000
                                                  //| (ss = 10^2 allows stack of size ,1001)
                                                  //| fact = 10000
                                                  //| (ss = 10^3 allows stack of size ,10001)
                                                  //| (ss = 10^4 allows stack of size ,1336)
                                                  //| (ss = 10^5 allows stack of size ,5456)
                                                  //| (ss = 10^6 allows stack of size ,62736)
                                                  //| (ss = 10^7 allows stack of size ,623876)
                                                  //| (ss = 10^8 allows stack of size ,6247732)
                                                  //| (ss = 10^9 allows stack of size ,62498160)

スレッドに割り当てられるスタックが指数関数的に増えると、スタックが指数関数的に深くなることがわかります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.