如何增加Java堆栈大小?
我问这个问题是为了了解如何增加 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 足以满足for
fact(1 << 19)
- -Xss18m 足以满足
fact(1 << 20)
- -Xss35m 足以满足
fact( 1 << 21)
- -Xss68m 足以满足
fact(1 << 22)
- -Xss129m 足以满足
fact(1 << 23 )
- -Xss258m 足以满足
fact(1 << 24)
- 足以满足
fact(1 << 25)
-Xss515m 从上面的数字看来,Java 为上面的函数使用每个堆栈帧大约 16 个字节,这是合理的。
上面的枚举包含可以足够而不是足够,因为堆栈要求是不确定的:使用相同的源文件和相同的多次运行它 - Xss...
有时会成功,有时会产生 StackOverflowError
。例如对于 1 << 20、-Xss18m
在 10 次运行中有 7 次就足够了,-Xss19m
也不总是足够,但是 -Xss20m
就足够了(总共 100 次,满分 100 次)。垃圾收集、JIT 启动或其他原因是否会导致这种不确定性行为?
在 StackOverflowError 处(也可能在其他异常处)打印的堆栈跟踪仅显示运行时堆栈的最新 1024 个元素。下面的答案演示了如何计算所达到的确切深度(可能比 1024 大得多)。
许多做出回应的人指出,考虑同一算法的替代、较少堆栈需求的实现是一种良好且安全的编码实践。一般来说,可以将一组递归函数转换为迭代函数(使用例如Stack
对象,该对象填充在堆上而不是运行时堆栈上)。对于这个特定的事实函数,转换它非常容易。我的迭代版本如下所示:
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
也会为大输入产生准确的结果。
I asked this question to get to know how to increase the runtime call stack size in the JVM. I've got an answer to this, and I've also got many useful answers and comments relevant to how Java handles the situation where a large runtime stack is needed. I've extended my question with the summary of the responses.
Originally I wanted to increase the JVM stack size so programs like runs without a 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));
}
}
The corresponding configuration setting is the java -Xss...
command-line flag with a large enough value. For the program TT
above, it works like this with OpenJDK's JVM:
$ javac TT.java
$ java -Xss4m TT
One of the answers has also pointed out that the -X...
flags are implementation dependent. I was using
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)
It is also possible to specify a large stack only for one thread (see in one of the answers how). This is recommended over java -Xss...
to avoid wasting memory for threads that don't need it.
I was curious how large a stack the program above exactly needs, so I've run it n
increased:
- -Xss4m can be enough for
fact(1 << 15)
- -Xss5m can be enough for
fact(1 << 17)
- -Xss7m can be enough for
fact(1 << 18)
- -Xss9m can be enough for
fact(1 << 19)
- -Xss18m can be enough for
fact(1 << 20)
- -Xss35m can be enough for
fact(1 << 21)
- -Xss68m can be enough for
fact(1 << 22)
- -Xss129m can be enough for
fact(1 << 23)
- -Xss258m can be enough for
fact(1 << 24)
- -Xss515m can be enough for
fact(1 << 25)
From the numbers above it seems that Java is using about 16 bytes per stack frame for the function above, which is reasonable.
The enumeration above contains can be enough instead of is enough, because the stack requirement is not deterministic: running it multiple times with the same source file and the same -Xss...
sometimes succeeds and sometimes yields a StackOverflowError
. E.g. for 1 << 20, -Xss18m
was enough in 7 runs out of 10, and -Xss19m
wasn't always enough either, but -Xss20m
was enough (in all 100 runs out of 100). Does garbage collection, the JIT kicking in, or something else cause this nondeterministic behavior?
The stack trace printed at a StackOverflowError
(and possibly at other exceptions as well) shows only the most recent 1024 elements of the runtime stack. An answer below demonstrates how to count the exact depth reached (which might be a lot larger than 1024).
Many people who responded has pointed out that it is a good and safe coding practice to consider alternative, less stack-hungry implementations of the same algorithm. In general, it is possible to convert to a set of recursive functions to iterative functions (using a e.g. Stack
object, which gets populated on the heap instead of on the runtime stack). For this particular fact
function, it is quite easy to convert it. My iterative version would look like:
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));
}
}
FYI, as the iterative solution above shows it, the fact
function cannot compute the exact factorial of numbers above 65 (actually, even above 20), because the Java built-in type long
would overflow. Refactoring fact
so it would return a BigInteger
instead of long
would yield exact results for large inputs as well.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
嗯...它对我有用,并且堆栈远小于 999MB:
(Windows JDK 7、构建 17.0-b05 客户端 VM 和 Linux JDK 6 - 与您发布的版本信息相同)
Hmm... it works for me and with far less than 999MB of stack:
(Windows JDK 7, build 17.0-b05 client VM, and Linux JDK 6 - same version information as you posted)
我假设您通过堆栈跟踪中的重复行计算了“1024 的深度”?
显然,Throwable 中的堆栈跟踪数组长度似乎被限制为 1024。
尝试以下程序:
I assume you calculated the "depth of 1024" by the recurring lines in the stack trace?
Obviously, the stack trace array length in Throwable seems to be limited to 1024.
Try the following program:
如果您想调整线程堆栈大小,您需要查看 Hotspot JVM 上的 -Xss 选项。在非 Hotspot VM 上可能有所不同,因为 JVM 的 -X 参数是特定于发行版的 IIRC。
在 Hotspot 上,如果您想将大小设置为 16 兆,这看起来像
java -Xss16M
。如果您想查看可以传入的所有特定于发行版的 JVM 参数,请输入 java -X -help 。我不确定这在其他 JVM 上是否同样有效,但它会打印所有特定于 Hotspot 的参数参数。
不管怎样,我建议限制 Java 中递归方法的使用。它在优化它们方面并不是很好 - 一方面,JVM 不支持尾递归(请参阅 JVM 是否会阻止尾部调用优化?)。尝试重构上面的阶乘代码以使用 while 循环而不是递归方法调用。
If you want to play with the thread stack size, you'll want to look at the -Xss option on the Hotspot JVM. It may be something different on non Hotspot VM's since the -X parameters to the JVM are distribution specific, IIRC.
On Hotspot, this looks like
java -Xss16M
if you want to make the size 16 megs.Type
java -X -help
if you want to see all of the distribution specific JVM parameters you can pass in. I am not sure if this works the same on other JVMs, but it prints all of Hotspot specific parameters.For what it's worth - I would recommend limiting your use of recursive methods in Java. It's not too great at optimizing them - for one the JVM doesn't support tail recursion (see Does the JVM prevent tail call optimizations?). Try refactoring your factorial code above to use a while loop instead of recursive method calls.
控制进程内堆栈大小的唯一方法是启动一个新的
线程
。但您也可以通过使用-Xss
参数创建自调用子 Java 进程来进行控制。The only way to control the size of stack within process is start a new
Thread
. But you can also control by creating a self-calling sub Java process with the-Xss
parameter.很难给出明智的解决方案,因为您热衷于避免所有理智的方法。重构一行代码是明智的解决方案。
注意:使用 -Xss 设置每个线程的堆栈大小,这是一个非常糟糕的主意。
另一种方法是字节码操作来更改代码,如下所示;
给出 n > 的每个答案127 是 0。这样可以避免更改源代码。
It is hard to give a sensible solution since you are keen to avoid all sane approaches. Refactoring one line of code is the senible solution.
Note: Using -Xss sets the stack size of every thread and is a very bad idea.
Another approach is byte code manipulation to change the code as follows;
given every answer for n > 127 is 0. This avoid changing the source code.
将此选项添加
到您的 Spark-submit 命令将解决此问题。
Add this option
to your spark-submit command will fix this issue.
我做了Anagram excersize,就像计数找零问题,但面额为 50 000 枚(硬币)。我不确定它是否可以迭代完成,我不在乎。我只知道 -xss 选项没有效果——我总是在 1024 个堆栈帧后失败(可能是 scala 在传递到 java 或 printStackTrace 限制方面做得不好。我不知道)。无论如何,这是一个糟糕的选择。您不希望应用程序中的所有线程都是巨大的。但是,我用新线程(堆栈大小)做了一些实验。这确实有效,
您会看到,随着分配给线程的堆栈呈指数级增长,堆栈可以呈指数级增长。
I did Anagram excersize, which is like Count Change problem but with 50 000 denominations (coins). I am not sure that it can be done iteratively, I do not care. I just know that the -xss option had no effect -- I always failed after 1024 stack frames (might be scala does bad job delivering to to java or printStackTrace limitation. I do not know). This is bad option, as explained anyway. You do not want all threads in to app to be monstrous. However, I did some experiments with new Thread (stack size). This works indeed,
You see that stack can grow exponentially deeper with exponentially more stack alloted to the thread.
诡异的!你是说你想生成一个 1<<15 深度的递归???!!!!!!
我建议不要尝试。堆栈的大小将为
2^15 * sizeof(stack-frame)
。我不知道堆栈帧大小是多少,但 2^15 是 32.768。差不多...好吧,如果它停在 1024 (2^10),您必须将其放大 2^5 倍,即比实际设置大 32 倍。Weird! You are saying that you want to generate a recursion of 1<<15 depth???!!!!
I'd suggest DON'T try it. The size of the stack will be
2^15 * sizeof(stack-frame)
. I don't know what stack-frame size is, but 2^15 is 32.768. Pretty much... Well, if it stops at 1024 (2^10) you'll have to make it 2^5 times bigger, it is, 32 times bigger than with your actual setting.其他发帖者指出了如何增加记忆力,并且可以记住电话。我建议对于许多应用,您可以使用斯特林公式来近似大 n!速度非常快,几乎没有内存占用。
看一下这篇文章,其中对函数和代码进行了一些分析:
http ://twobrothers.org/brendan/blog/stirlings-approximation-formula-clojure/
Other posters have pointed out how to increase memory and that you could memoize calls. I'd suggest that for many applications, you can use Stirling's formula to approximate large n! very quickly with almost no memory footprint.
Take a gander at this post, which has some analysis of the function and code:
http://threebrothers.org/brendan/blog/stirlings-approximation-formula-clojure/