堆栈溢出从Java深层recursion?

在使用函数式语言的一些经验之后,我开始在Java中使用更多的recursion – 但是这种语言似乎有一个相对较浅的大约1000的调用堆栈。

有没有办法让调用堆栈更大? 就像在Erlang一样,我可以使function深入数百万次吗?

我在做Project Euler问题时越来越注意到这一点。

谢谢。

我想你可以使用这些参数

-s Stacksize增加本地堆栈大小或

-oss Stacksize可以增加Java堆栈的大小,

默认的本地堆栈大小为128k,最小值为1000字节。 默认的Java堆栈大小为400k,最小值为1000字节。

http://edocs.bea.com/wls/docs61/faq/java.html#251197

编辑:

在阅读第一条评论(Chuck's)之后,以及重读这个问题并阅读另一个答案,我想澄清一下,我把这个问题解释为“增加堆栈大小”。 我没有打算说,你可以有无限的堆栈,比如在函数式编程(一种我只是抓住其表面的编程范例)中。

增加堆叠尺寸只能作为临时绷带。 正如其他人已经指出的,你真正想要的是tail call elimination,而Java由于各种原因没有这个。 但是,如果你愿意,你可以作弊。

手中的红丸? 好的,请这边。

有许多方法可以在栈中交换堆栈。 例如,不要在函数内进行recursion调用,而是在返回一个懒惰的数据结构 ,在评估时进行调用。 然后,您可以用Java的for-construct来展开“堆栈”。 我将用一个例子来演示。 考虑这个Haskell代码:

map :: (a -> b) -> [a] -> [b] map _ [] = [] map f (x:xs) = (fx) : map f xs 

请注意,这个函数永远不会评估列表的尾部。 所以这个函数实际上并不需要进行recursion调用。 在Haskell中,它实际上会为尾部返回一个thunk ,如果需要的话就会调用它。 我们可以在Java中做同样的事情(它使用Functional Java中的类):

 public <B> Stream<B> map(final F<A, B> f, final Stream<A> as) {return as.isEmpty() ? nil() : cons(ff(as.head()), new P1<Stream<A>>() {public Stream<A> _1() {return map(f, as.tail);}});} 

请注意, Stream<A>Atypes的值和P1types的值组成,类似于当调用_1()时返回stream的其余部分的thunk。 虽然它看起来像recursion,但是对映射的recursion调用却不成立,但成为Stream数据结构的一部分。

这可以通过一个常规的构造来解开。

 for (Stream<B> b = bs; b.isNotEmpty(); b = b.tail()._1()) {System.out.println(b.head());} 

这是另一个例子,因为你在谈论欧拉项目。 这个程序使用了相互recursion的函数,并且不会堆栈,即使是数百万次调用:

 import fj.*; import fj.data.Natural; import static fj.data.Enumerator.naturalEnumerator; import static fj.data.Natural.*; import static fj.pre.Ord.naturalOrd; import fj.data.Stream; import fj.data.vector.V2; import static fj.data.Stream.*; import static fj.pre.Show.*; public class Primes {public static Stream<Natural> primes() {return cons(natural(2).some(), new P1<Stream<Natural>>() {public Stream<Natural> _1() {return forever(naturalEnumerator, natural(3).some(), 2) .filter(new F<Natural, Boolean>() {public Boolean f(final Natural n) {return primeFactors(n).length() == 1;}});}});} public static Stream<Natural> primeFactors(final Natural n) {return factor(n, natural(2).some(), primes().tail());} public static Stream<Natural> factor(final Natural n, final Natural p, final P1<Stream<Natural>> ps) {for (Stream<Natural> ns = cons(p, ps); true; ns = ns.tail()._1()) {final Natural h = ns.head(); final P1<Stream<Natural>> t = ns.tail(); if (naturalOrd.isGreaterThan(h.multiply(h), n)) return single(n); else {final V2<Natural> dm = n.divmod(h); if (naturalOrd.eq(dm._2(), ZERO)) return cons(h, new P1<Stream<Natural>>() {public Stream<Natural> _1() {return factor(dm._1(), h, t);}});}}} public static void main(final String[] a) {streamShow(naturalShow).println(primes().takeWhile (naturalOrd.isLessThan(natural(Long.valueOf(a[0])).some())));}} 

你可以做的另一件事是堆栈堆栈是使用多个线程 。 这个想法是,不是做recursion调用, 而是创build一个调用thunk的thunk,把这个thunk交给一个新的线程,让当前的线程退出这个函数。 这就是Stackless Python之类的东西。

以下是Java中的一个例子。 抱歉,如果没有import static子句,看起来有点不透明:

 public static <A, B> Promise<B> foldRight(final Strategy<Unit> s, final F<A, F<B, B>> f, final B b, final List<A> as) {return as.isEmpty() ? promise(s, Pp(b)) : liftM2(f).f (promise(s, Pp(as.head()))).f (join(s, new P1<Promise<B>>>() {public Promise<B> _1() {return foldRight(s, f, b, as.tail());}}));} 

Strategy<Unit> s由一个线程池支持,并且promise函数将一个thunk传递给线程池,返回Promise ,这非常类似于java.util.concurrent.Future ,只是更好。 看这里。 重点在于上面的方法在O(1)堆栈中将右recursion数据结构向右折叠 ,这通常需要尾部消除。 所以我们有效地实现了TCE,以换取一些复杂性。 你可以这样调用这个函数:

 Strategy<Unit> s = Strategy.simpleThreadStrategy(); int x = foldRight(s, Integers.add, List.nil(), range(1, 10000)).claim(); System.out.println(x); // 49995000 

请注意,后一种技术非常适合非线性recursion。 也就是说,它将运行在不具有尾部调用的常量栈甚至algorithm中。

你可以做的另一件事是采用一种叫做蹦床的技巧。 蹦床是一种计算,被称为数据结构,可以通过。 function性Java库包含我编写的Trampoline数据types,可以让您将任何函数调用变成尾声调用。 作为一个例子, 这里是一个trampolined foldRightC向右折叠在不断的堆栈中:

 public final <B> Trampoline<B> foldRightC(final F2<A, B, B> f, final B b) {return Trampoline.suspend(new P1<Trampoline<B>>() {public Trampoline<B> _1() {return isEmpty() ? Trampoline.pure(b) : tail().foldRightC(f, b).map(ff(head()));}});} 

这与使用multithreading的原理是一样的,除了不是在自己的线程中调用每一步,而是在堆上构造每一步,就像使用Stream ,然后我们用Trampoline.run在一个循环中运行所有的步骤Trampoline.run

这取决于JVM是否使用尾recursion – 我不知道它们中的任何一个是否正确,但是您不应该依赖它。 特别是,改变堆栈的大小很less是正确的,除非你实际使用了多lessrecursion级别的硬性限制,而且你确切知道每个堆栈空间会占用多less。 非常脆弱。

基本上,你不应该使用无法为其构build的语言使用无界recursion。 恐怕你必须使用迭代。 是的,这可能是一个轻微的痛苦有时:(

如果你不得不问,你可能做错了什么 。

现在,虽然你可能会find一种方法来增加java中的默认堆栈,但是我只需要添加我的2分钱,因为你真的需要find另一种方法来做你想做的事情,而不是依靠增加的堆栈。

由于Java规范并没有强制JVM执行尾recursion优化技术,解决这个问题的唯一方法是通过减less需要保留的局部variables/参数的数量来降低堆栈压力追踪,或理想地通过显着降低recursion水平,或者只是重写而没有recursion。

大多数函数式语言都支持尾recursion。 但是,大多数Java编译器不支持这一点。 相反,它进行另一个函数调用。 这意味着你可以做recursion调用总是有一个上限(因为你最终会用完堆栈空间)。

使用尾recursion可以重用正在recursion的函数的堆栈框架,所以在堆栈上没有相同的约束条件。

你可以在命令行上设置它:

java -Xss8M类

运行在Java虚拟机上的Clojure非常希望实现tail调用优化,但是由于JVM字节码的限制(我不知道细节),它不能实现。 因此,它只能以一种特殊的“重复”forms来帮助自己,这种forms实现了一些你期望从适当的尾recursion中获得的基本特征。

无论如何,这意味着JVM目前无法支持尾部呼叫优化。 我强烈build议不要使用recursion作为JVM上的一般循环结构。 我个人的观点是,Java不是一个足够高的语言水平。

 public static <A, B> Promise<B> foldRight(final Strategy<Unit> s, final F<A, F<B, B>> f, final B b, final List<A> as) { return as.isEmpty() ? promise(s, Pp(b)) : liftM2(f).f(promise(s, Pp(as.head()))) .f(join(s, new F<List<A>, P1<Promise<B>>>() { public Promise<B> f(List<A> l) { return foldRight(s, f, b, l); } }.f(as.tail()))); } 

我遇到了同样的问题,并最终重写recursion到for循环 ,并做了诀窍。

在eclipse中如果你正在使用,请将-xss2m设置为vm参数。

要么

-xss2m直接在命令行上。

 java -xss2m classname