如何增加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

答案之一还指出,这些-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个字节,这是合理的。

上面包含的枚举可能足够,而不是足够,因为堆栈要求不是确定性的:使用相同的源文件多次运行它,并且-Xss...有时成功一次,有时会产生a 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以使其返回a BigInteger而不是long大型输入也能产生准确的结果。


看起来比它简单。fact()递归调用32K次。那应该少于1MB的堆栈。:-/
亚伦·迪古拉

@Aaron:+函数开销,..很多
Halfdan

4
除了堆栈问题。请注意,您正在炸毁您的long和int。1 << 4是我在变为负数然后变为0之前可以使用的最大值。尝试使用BigInteger
Sean 2010年

虽然不确定函数的开销实际上是多少,但我认为您仍然应该能够以几兆字节的堆栈空间顺序进行2 ^ 15调用。
尼尔·科菲

7
注意:您正在设置每个线程的堆栈大小并产生无意义的结果,所有这些都是为了避免重构一行代码。我很高兴您能优先处理您的优先事项。:P
彼得·劳里

Answers:


78

嗯...它对我有用,堆栈少于999MB:

> java -Xss4m Test
0

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


1
最有可能是我的评论,当我意识到与尼尔发布的内容相同时,便将其删除。
肖恩2010年

多亏了这个问题和您的回答,我才得以完成我的任务。我的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选项。在非Hotspot VM上可能有所不同,因为JVM的-X参数是特定于发行版的IIRC。

在Hotspot上,这看起来像是java -Xss16M要将大小设为16兆。

键入java -X -help是否要查看可以传递的所有特定于发行版的JVM参数。我不确定这是否在其他JVM上也可以使用,但是会打印所有Hotspot特定参数。

对于它的价值-我建议您限制在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...
pt

1
我对此感到很兴奋,但随后通读了docs.oracle.com/javase/6/docs/api/java/lang/Thread.html#Thread-stacksize构造函数-激动了。
kellogs 2013年

我想知道当文档仅说“在某些平台上”时,它们是哪个平台
Dennis C

3

添加此选项

--driver-java-options -Xss512m

您的spark-submit命令可以解决此问题。


2

由于您渴望避免使用所有理智的方法,因此很难给出明智的解决方案。重构一行代码是明智的解决方案。

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

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

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

如果n> 127的每个答案都为0,则可以避免更改源代码。


1
感谢您指出,设置高堆栈大小会浪费不必要的线程内存。也感谢您指出问题中的fact函数可以重构为使用更少的堆栈空间。
pt

1
@pts,表示感谢。考虑到更复杂的用例,我认为这是一个明智的问题,但这很少见。
彼得·劳瑞

0

奇怪的!您是说要生成1 << 15深度递归 ??? !!!!

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



0

我做了Anagram excersize,就像Count Change问题一样,但是有5万种面额(硬币)。我不确定是否可以迭代完成,我不在乎。我只知道-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)

您会看到,分配给线程的堆栈可以按指数级增长,而堆栈可以按指数级增长。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.