Java中深度递归的堆栈溢出?
在对函数式语言有了一些经验之后,我开始在Java中更多地使用递归——但该语言似乎有一个相对较浅的调用堆栈,大约1000个 有没有办法使调用堆栈变大?比如,我可以像在Erlang中那样生成数百万次调用深度的函数吗 当我处理Euler问题时,我越来越注意到这一点Java中深度递归的堆栈溢出?,java,functional-programming,stack,overflow,Java,Functional Programming,Stack,Overflow,在对函数式语言有了一些经验之后,我开始在Java中更多地使用递归——但该语言似乎有一个相对较浅的调用堆栈,大约1000个 有没有办法使调用堆栈变大?比如,我可以像在Erlang中那样生成数百万次调用深度的函数吗 当我处理Euler问题时,我越来越注意到这一点 谢谢。我想您可以使用这些参数 -ss Stacksize以增加本机 堆栈大小或 -oss Stacksize增加了Java 堆栈大小 默认本机堆栈大小为128k, 最小值为1000字节。 默认java堆栈大小为400k, 最小值为1000字
谢谢。我想您可以使用这些参数 -ss Stacksize以增加本机 堆栈大小或 -oss Stacksize增加了Java 堆栈大小 默认本机堆栈大小为128k, 最小值为1000字节。 默认java堆栈大小为400k, 最小值为1000字节 编辑:
在阅读了第一条评论(Chuck’s)以及重新阅读问题和其他答案后,我想澄清一下,我将问题解释为“增加堆栈大小”。我不想说你可以有无限的堆栈,比如在函数式编程中(我只是触及了它的表面)。大多数函数式语言都支持尾部递归。但是,大多数Java编译器都不支持这一点。相反,它会进行另一个函数调用。这意味着您可以进行的递归调用的数量始终有一个上限(因为您最终将耗尽堆栈空间)
对于尾部递归,您可以重用正在递归的函数的堆栈框架,因此堆栈上没有相同的约束。是否使用尾部递归取决于JVM-我不知道它们是否使用尾部递归,但您不应该依赖它。特别是,更改堆栈大小很少是正确的做法,除非您对实际使用的递归级别有严格限制,并且您确切地知道每个级别将占用多少堆栈空间。非常脆弱 基本上,您不应该在不是为其构建的语言中使用无界递归。恐怕您将不得不使用迭代。是的,有时会有轻微的疼痛:() 现在,虽然您可能可以找到一种方法来增加java中的默认堆栈,但让我添加我的2美分,因为您确实需要找到另一种方法来做您想做的事情,而不是依赖于增加的堆栈
由于java规范没有强制要求JVM实现尾部递归优化技术,因此解决此问题的唯一方法是减少堆栈压力,或者减少需要跟踪的局部变量/参数的数量,或者理想情况下,通过显著降低递归级别,或者完全不使用递归重写。您可以在命令行上设置:
java-Xss8M类Clojure运行在java虚拟机上,非常希望实现尾部调用优化,但由于JVM字节码中的限制(我不知道细节),它不能实现。因此,它只能通过一个特殊的“重复”来帮助自己表单,它实现了正确的尾部递归所期望的一些基本特性
无论如何,这意味着JVM目前无法支持尾部调用优化。我强烈建议不要在JVM上使用递归作为一般循环结构。我个人的观点是Java不是一种足够高级的语言。增加堆栈大小只会起到暂时的束缚作用。正如其他人所指出的,w您真正想要的是尾部调用消除,Java由于各种原因没有这样做。但是,如果您愿意,您可以作弊 手里拿着红色药丸?好的,请这边走
有一些方法可以交换堆栈,例如,在函数内不调用递归调用,返回一个强的>惰性数据结构<强>,这样调用时就可以调用该调用。然后,可以用java的结构解压缩“栈”。
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = (f x) : map f xs
请注意,此函数从不计算列表的尾部。因此,该函数实际上不需要进行递归调用。在Haskell中,它实际上返回尾部的thunk,如果需要,将调用该尾部。我们可以在Java中执行相同的操作(这使用来自的类):
这是另一个例子,因为您在谈论Project Euler。该程序使用相互递归的函数,即使是数百万次调用,也不会破坏堆栈:
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())));}}
strategys
由线程池支持,而promise
函数将thunk传递给线程池,返回一个promise
,非常类似于java.util.concurrent.Future
,只是更好。重点是上面的方法在O(1)中将一个右递归数据结构向右折叠堆栈,通常需要消除尾部调用。因此,我们有效地实现了TCE,以换取一些复杂性。您可以按如下方式调用此函数:
Strategy<Unit> s = Strategy.simpleThreadStrategy();
int x = foldRight(s, Integers.add, List.nil(), range(1, 10000)).claim();
System.out.println(x); // 49995000
这与使用多个线程的原理相同,不同的是,我们不是在自己的线程中调用每个步骤,而是在堆上构造每个步骤,非常类似于使用流
,然后使用蹦床在单个循环中运行所有步骤。运行公共静态承诺foldRight(最终策略s,
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, P.p(b))
: liftM2(f).f(promise(s, P.p(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())));
}
最后的F,
最后的B,
最终名单(见附件)
{
以.isEmpty()的形式返回?承诺(s,P.P(b))
:liftM2(f).f(promise(s,P.P(as.head()))
.f(加入,新的f()
{
公共承诺f(列表l)
{
返回文件夹(s、f、b、l);
}
}.f(as.tail());
}
在eclipse中,如果正在使用,请将-xss2m设置为vm参数
或
-xss2m直接在命令行上
java -xss2m classname
我遇到了同样的问题,结果将递归重写为for
Strategy<Unit> s = Strategy.simpleThreadStrategy();
int x = foldRight(s, Integers.add, List.nil(), range(1, 10000)).claim();
System.out.println(x); // 49995000
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(f.f(head()));}});}
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, P.p(b))
: liftM2(f).f(promise(s, P.p(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())));
}
java -xss2m classname