BigInteger内存泄漏导致Java中的堆栈溢出

时间:2015-06-18 23:15:40

标签: java stack-overflow biginteger fibonacci

我正在尝试编写一个优化的fibonacci作为能够计算fib(300)和fib(8000)的赋值。这是我到目前为止(map是一个HashMap)。

public static BigInteger fibMemo(int n) {

    if (n <= 1){
        return BigInteger.valueOf(n);
    }

    if (!map.containsKey(n)){
        map.put(n, fibMemo(n-1).add(fibMemo(n-2)));
    }
    return map.get(n);        
}

当我打电话

System.out.println("300: " + FibonacciMemo.fibMemo(300));

自己,它运作得很好。 此外,

System.out.println("8000: " + FibonacciMemo.fibMemo(8000));
如果我注释掉之前对fib(300)的调用,

工作正常。但是,如果我保持两个调用,我会在递归fibMemo上获得堆栈溢出。这对我来说似乎很奇怪。有人可以澄清一下情况吗?提前谢谢。

以下是代码:

import java.util.HashMap; // Import Java's HashMap so we can use it
import java.math.BigInteger;

public class FibonacciMemo {
    private static HashMap<Integer, BigInteger> map = new HashMap<Integer, BigInteger>();
    /**
     * The classic recursive implementation with no memoization. Don't care
     * about graceful error catching, we're only interested in performance.
     * 
     * @param n
     * @return The nth fibonacci number
     */
    public static int fibNoMemo(int n) {
        if (n <= 1) {
            return n;
        }
        return fibNoMemo(n - 2) + fibNoMemo(n - 1);
    }
    /**
     * Your optimized recursive implementation with memoization. 
     * You may assume that n is non-negative.
     * 
     * @param n
     * @return The nth fibonacci number
     */
    public static BigInteger fibMemo(int n) {
        // YOUR CODE HERE
        if (n <= 1){
            return BigInteger.valueOf(n);
        }

        if (!map.containsKey(n)){
            map.put(n, fibMemo(n-1).add(fibMemo(n-2)));
        }
        return map.get(n);        
    }
public static void main(String[] args) {
        // Optional testing here        
        String m = "Fibonacci's real name was Leonardo Pisano Bigollo.";
        m += "\n" + "He was the son of a wealthy merchant.\n";
        System.out.println(m);
         System.out.println("300: " + FibonacciMemo.fibMemo(300));
        System.out.println("8000: " + FibonacciMemo.fibMemo(8000));
        // 46th Fibonacci = 1,836,311,903
        // 47th Fibonacci = 2,971,215,073
    }
}

4 个答案:

答案 0 :(得分:2)

您的代码存在两个问题。显而易见的是堆栈消耗。 memoization确实将时间复杂度从指数降低到线性,但是,该方法仍然具有线性堆栈消耗 - 对于输入值8000,它分配8000个堆栈帧。

正如docs中所述,每个线程的默认堆栈大小 320kB ,大​​约需要1000到2000帧,这还不够。您可以使用-Xss JVM开关增加堆栈大小,但这仍然不是防弹。您应该使用迭代实现。

第二个问题是你的静态缓存永远不会被清除,这基本上会导致内存泄漏。您可以将递归方法包装在另一个递归方法中,该方法在递归终止后清除散列映射,但这会丢弃一些性能,因为一次调用的结果不能在以下方法中重用。

更高性能的解决方案是使用不需要手动清理的适当缓存实现,但是自己处理大小限制和垃圾收集。 Guava提供了此类实施。

答案 1 :(得分:1)

看起来你的递归算法对于Java的默认堆栈大小来说太多了。堆栈内存的优化与硬件中的堆不同,无论如何都不应该使用这种递归算法。有些语言可以优化尾递归。至少在这种情况下,Java似乎不会总是优化您的代码。

因此,最好的解决方案就是重写代码以改为使用循环。

   private final static List<BigInteger> fibs = new ArrayList<>();
   static{ fibs.add( BigInteger.ZERO ); fibs.add( BigInteger.ONE ); }

   public static BigInteger lFib( int n ) {
      if( n < 0 ) throw new IllegalArgumentException();
      if( n >= fibs.size() ) {
         for( int i = fibs.size(); i <= n; i++ )
            fibs.add( fibs.get(i-2).add( fibs.get(i-1) ) );
      }
      return fibs.get(n);
   }

非常轻微的测试。

答案 2 :(得分:0)

问题是线程堆栈的大小,它可能会耗尽大量的递归调用。解决方案是提供足够的堆栈大小。您可以尝试使用vm arg -Xss运行该应用。我尝试使用-Xss2m并且效果很好。

答案 3 :(得分:0)

更改您的代码

        map.put(n, fibMemo(n-1).add(fibMemo(n-2)));

        map.put(n, fibMemo(n-2).add(fibMemo(n-1)));

工作正常。

前者的呼叫序列是

fibMemo(10) nested level = 0
fibMemo(9) nested level = 1
fibMemo(8) nested level = 2
fibMemo(7) nested level = 3
fibMemo(6) nested level = 4
fibMemo(5) nested level = 5
fibMemo(4) nested level = 6
fibMemo(3) nested level = 7
fibMemo(2) nested level = 8
fibMemo(1) nested level = 9
fibMemo(0) nested level = 9
fibMemo(1) nested level = 8
fibMemo(2) nested level = 7
fibMemo(3) nested level = 6
fibMemo(4) nested level = 5
fibMemo(5) nested level = 4
fibMemo(6) nested level = 3
fibMemo(7) nested level = 2
fibMemo(8) nested level = 1

后者的呼叫序列是

fibMemo(10) nested level = 0
fibMemo(8) nested level = 1
fibMemo(6) nested level = 2
fibMemo(4) nested level = 3
fibMemo(2) nested level = 4
fibMemo(0) nested level = 5
fibMemo(1) nested level = 5
fibMemo(3) nested level = 4
fibMemo(1) nested level = 5
fibMemo(2) nested level = 5
fibMemo(5) nested level = 3
fibMemo(3) nested level = 4
fibMemo(4) nested level = 4
fibMemo(7) nested level = 2
fibMemo(5) nested level = 3
fibMemo(6) nested level = 3
fibMemo(9) nested level = 1
fibMemo(7) nested level = 2
fibMemo(8) nested level = 2

后者对堆栈的消耗较少。