2010-09-13 172 views
88

我问过这个问题,以了解如何增加JVM中的运行时调用堆栈大小。我已经得到了一个答案,并且我还得到了许多有用的答案和评论,这些答案和评论与Java如何处理需要大型运行时堆栈的情况相关。我已经回答了问题的总结。如何增加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可以连接足够ough为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会产生精确的结果大投入为好。

+0

看起来比它更简单。 fact()被递归地调用32K次。这应该小于1MB的堆栈。 : -/ – 2010-09-13 12:51:08

+0

@Aaron:+函数开销,这是..一个LOT – halfdan 2010-09-13 12:53:04

+4

除了你的堆栈问题。请注意,你正在炸毁你的长整数。 1 << 4是我在使用负数之前可以使用的最大值,然后变为0.尝试使用BigInteger – Sean 2010-09-13 13:36:00

回答

59

嗯......我的作品,并与远小于栈的999MB:

> java -Xss4m Test 
0 

(视窗JDK 7,建设17.0-B05的客户端虚拟机和Linux JDK 6 - 相同版本的信息,你发布)

+1

很可能是我的评论,当我意识到Neil发布的同样的东西时,我删除了它。 – Sean 2010-09-13 14:37:03

+0

感谢这个问题和你的回答,我设法完成了我的assigment。我的DFS函数必须在具有〜10^5个顶点的图上递归。最后,它与-Xss129m:D – bholagabbar 2015-08-09 11:31:06

9

如果您想玩线程堆栈大小,您需要查看热点JVM上的-Xss选项。由于到JVM的-X参数是特定于分布的,IIRC,所以在非热点虚拟机上可能会有所不同。

在Hotspot上,如果您想将大小设置为16 megs,则看起来像java -Xss16M

类型java -X -help如果您想查看您可以传入的所有特定于分发的JVM参数,我不确定这是否与其他JVM相同,但它会打印所有热点特定参数。

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

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); 
    } 
} 
+4

+1一起用于实际深度检查... – helios 2010-09-15 14:25:07

0

奇怪!你说你想生成一个递归1个< < 15深度 ??? !!!!的

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

+3

这是正确的,但它没有回答我的问题。 – pts 2010-09-17 13:58:10

7

控制进程内堆栈大小的唯一方法是启动新的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); 
     } 
    } 

} 
+0

感谢您提供的信息丰富的答案,除了'java -Xss ...'以外,了解其他选项还是很好的。 – pts 2010-09-17 18:20:31

+1

我对此感到兴奋,但随后阅读了http://docs.oracle.com/javase/6/docs/api/java/lang/Thread.html#Thread - stacksize构造函数 - 兴奋消失了。 – kellogs 2013-12-09 13:53:42

+0

我不知道哪些平台,他们当文档只说 - “在某些平台上” – 2014-12-05 01:02:22

1

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

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

另一种方法是通过字节码操作来更改代码,如下所示;

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

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

+1

感谢您指出设置高堆栈大小会浪费内存的线程不需要它。还要感谢您指出,问题中的“事实”函数可以重构为使用更少的堆栈空间。 – pts 2010-09-17 22:14:47

+1

@pts,你的感谢是注意到的。我认为在一个更复杂的用例中这是一个明智的问题,但这些非常罕见。 – 2010-09-18 14:46:40

0

我做Anagram excersize,这就好比Count Change问题,但有50种000面额(硬币)。我是not sure that it can be done iteratively,我不在乎。我只知道-xss选项没有任何作用 - 在1024个堆栈帧之后我总是失败(可能是scala对java或printStackTrace的限制不好,但我不知道)。无论如何,这是不好的选择。你不希望所有线程进入应用程序是可怕的。但是,我用新的线程(堆栈大小)做了一些实验。这确实有效,

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) 

你会发现堆栈可以以指数级增长的指数级增长,并且指派更多的堆栈分配给该线程。

1

添加此选项

--driver-java的选项-Xss512m

到您的火花提交命令将解决这个问题。