如何增加Java堆栈大小?

时间:2010-09-13 12:43:18

标签: java stack stack-overflow

我问了这个问题,以了解如何在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)

也可以仅为一个线程指定一个大堆栈(参见其中一个答案)。建议使用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个字节用于上述函数,这是合理的。

上面的枚举包含足够而不是足够,因为堆栈要求不是确定性的:使用相同的源文件和相同的{运行它多次{ {1}}有时会成功,有时会产生-Xss...。例如。对于1&lt;&lt; 20,{10}中有7次出现StackOverflowError就足够了,-Xss18m也不够,但是-Xss19m就足够了(在所有100次100次中)。垃圾收集,JIT踢入或其他什么导致这种不确定行为?

-Xss20m处打印的堆栈跟踪(以及可能还有其他例外)仅显示运行时堆栈的最新1024个元素。下面的答案演示了如何计算达到的确切深度(可能比1024大得多)。

许多回复的人指出,考虑使用相同算法的替代,少堆栈的实现,这是一种安全的编码实践。通常,可以将一组递归函数转换为迭代函数(使用例如StackOverflowError对象,该对象在堆上而不是在运行时堆栈上填充。对于这个特定的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)); } } 函数无法计算65以上(实际上甚至超过20)的数字的精确因子,因为Java内置类型fact会溢出。重构long以便它返回fact而不是BigInteger也会产生大量输入的精确结果。

9 个答案:

答案 0 :(得分:69)

嗯......它对我有用,而且堆栈远不到999MB:

> java -Xss4m Test
0

(Windows JDK 7,构建17.0-b05客户端VM和Linux JDK 6 - 与您发布的版本信息相同)

答案 1 :(得分: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);
    }
}

答案 2 :(得分:9)

如果要使用线程堆栈大小,您需要查看Hotspot JVM上的-Xss选项。在非Hotspot VM上可能会有所不同,因为JVM的-X参数是特定于分发的,IIRC。

在Hotspot上,如果你想要大小为16兆,这看起来像java -Xss16M

如果要查看可以传入的所有特定于发行版的JVM参数,请键入java -X -help。我不确定它是否在其他JVM上运行相同,但它会打印所有Hotspot特定参数。

对于它的价值 - 我建议限制你在Java中使用递归方法。它在优化它们方面并不太好 - 因为JVM不支持尾递归(参见Does the JVM prevent tail call optimizations?)。尝试重构上面的析因代码以使用while循环而不是递归方法调用。

答案 3 :(得分: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);
        }
    }

}

答案 4 :(得分:3)

添加此选项

--driver-java-options -Xss512m

到你的spark-submit命令将解决这个问题。

答案 5 :(得分:1)

很难给出合理的解决方案,因为你热衷于避免所有理智的方法。重构一行代码是可行的解决方案。

注意:使用-Xss设置每个线程的堆栈大小,这是一个非常糟糕的主意。

另一种方法是字节码操作,以便按如下方式更改代码;

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

给出n>的每个答案127为0.这样可以避免更改源代码。

答案 6 :(得分:0)

奇怪!你是说要生成递归1&lt;&lt;&lt; 15&lt; 15&gt; ???!

我建议不要尝试。堆栈的大小为2^15 * sizeof(stack-frame)。我不知道堆栈帧大小是多少,但2 ^ 15是32.768。差不多......好吧,如果它停在1024(2 ^ 10),你必须要大2 ^ 5倍,它比实际设置大32倍。

答案 7 :(得分:0)

其他海报已经指出了如何增加内存并且你可以记住调用。我建议对于许多应用程序,你可以使用斯特林的公式近似大n!很快就几乎没有内存占用。

请看这篇文章,其中对函数和代码进行了一些分析:

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

答案 8 :(得分:0)

我做了Anagram excersize,这就像Count Change问题,但有5万种面额(硬币)。我not sure that it can be done iteratively,我不在乎。我只知道-xss选项没有任何效果 - 我总是在1024个堆栈帧之后失败(可能是scala在传递给java或printStackTrace限制时做错了。我不知道)。无论如何,这是不好的选择。您不希望应用程序中的所有线程都是怪异的。但是,我用新的Thread(堆栈大小)做了一些实验。这确实有效,

  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)

你看到堆栈可以指数级地增长,并且指向更多的堆栈分配给线程。