Java中的递归调用

  Java中不合理的使用递归调用,可能会导致栈内存溢出,这点是需要注意的。

  

  java将为每个线程维护一个栈,栈里将为每个方法保存一个栈帧,栈帧代表了一个方法的运行状态。 也就是我们常说的方法栈。最后一个为当前运行的栈帧。

  那么每一次方法调用会为新调用方法的生成一个栈帧,保存当前方法的栈帧状态,栈帧上下文切换,切换到最新的方法栈帧。

  在递归和循环之间选择时,应该优先选择的是循环而非递归,特别是要避免深度的递归。

  

  关于递归还需要了解的是尾递归调用,尾递归调用是可以被进行优化的。

  尾调用指的是一个方法或者函数的调用在另一个方法或者函数的最后一条指令中进行。下面定义了一个foo()函数作为例子:

int foo(int a) {
  a = a + 1;
  return func(a);
}

  尾调用,不只是尾递归,函数调用本身都可以被优化掉,变得跟goto操作一样。这就意味着,在函数调用前先把栈给设置好,调用完成后再恢复栈的这个操作(分别是prolog和epilog)可以被优化掉。

  函数式语言的开发人员经常使用递归,所以大多数函数式语言的解释器都会进行尾调用的优化。但是在Java中使用深度的递归一定要非常的小心,否则很有可能会导致栈溢出的发生。

  

  下面是不合理使用递归的例子:

package test;

public class RecursiveTest {
    /**
     * 递归实现
     *
     * @param n
     * @return
     */
    public static double recursive(long n) {
        if (n == 1) {
            return Math.log(1);
        } else {
            return Math.log(n) + recursive(n - 1);
        }
    }

    /**
     * 非递归实现
     *
     * @param n
     * @return
     */
    public static double directly(long n) {
        double result = 0;
        for (int i = 1; i <= n; i++) {
            result += Math.log(i);
        }
        return result;
    }

    public static void main(String[] args) {
        int i = 5000000;
        long test = System.nanoTime();
        long start1 = System.nanoTime();
        double r1 = recursive(i);
        long end1 = System.nanoTime();
        long start2 = System.nanoTime();
        double r2 = directly(i);
        long end2 = System.nanoTime();

        System.out.println("recursive result:" + r1);
        System.out.println("recursive time used:" + (end1 - start1));
        System.out.println("non-recursive result:" + r2);
        System.out.println("non-recursive time used:" + (end2 - start2));
    }
}

  

  JVM中可能导致内存溢出的其他原因还包括: 

  • 引用变量过多使用了Static修饰 如public staitc Student s;在类中的属性中使用 static修饰的最好只用基本类型或字符串。如public static int i = 0; //public static String str;
  • 使用了大量的递归或无限递归(递归中用到了大量的建新的对象)
  • 使用了大量循环或死循环(循环中用到了大量的新建的对象)
  • 是否使用了向数据库查询所有记录的方法。即一次性全部查询的方法,如果数据量超过10万多条了,就可能会造成内存溢出。所以在查询时应采用“分页查询”。
  • 是否有数组,List,Map中存放的是对象的引用而不是对象,因为这些引用会让对应的对象不能被释放。会大量存储在内存中。
  • 是否使用了“非字面量字符串进行+”的操作。因为String类的内容是不可变的,每次运行"+"就会产生新的对象,如果过多会造成新String对象过多,从而导致JVM没有及时回收而出现内存溢出。

  如String s1 = "My name";

  String s2 = "is";

  String s3 = "xuwei";

  String str = s1 + s2 + s3 +.........;这是会容易造成内存溢出的

  

时间: 2024-08-24 02:01:02

Java中的递归调用的相关文章

2018.3.31 java中的递归

java中的递归 1.概念 定义一个方法时,出现本方法调用本方法的过程,称之为递归 2.特点 必然有一个边界条件 使用递归代码往往更简洁,可读性强 3.什么时候使用递归 n的阶乘和n的累加定义 f(n) =1 f(n)=f(n)*f(n-1) 4.普通实现与递归实现的比较 普通实现 //计算5的阶乘 public class Demo { public static void main(String[] args) { int sum = 1; for (int i =5; i >=1; i--

尾递归 递归函数中,递归调用是整个函数体中最后的语句,且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归,空间复杂度是O(1)

什么是递归深度 递归深度就是递归函数在内存中,同时存在的最大次数. 例如下面这段求阶乘的代码: Java: int factorial(int n) { if (n == 1) { return 1; } return factorial(n - 1) * n; } Python: def factorial(n): if n == 1: return 1 return factorial(n-1) * n C++: int factorial(int n) { if (n == 1) { re

C#函数式编程中的递归调用之尾递归详解

关于递归相信大家已经熟悉的不能再熟悉了,所以笔者在这里就不多费口舌,不懂的读者们可以在博客园中找到很多与之相关的博客.下面我们直接切入正题,开始介绍尾递归. 尾递归 普通递归和尾递归如果仅仅只是从代码的角度出发来看,我们可能发现不了他的特点,所以笔者利用两张堆栈上的图来展示具体的差距在哪,首先我们来看看普通的递归调用的情况,如下图1.1所示: 假设这里执行的函数是Func1,并且Func1中通过递归调用了自己,那么我们可以看到栈上在每次调用Func1的时候都会重新将函数返回地址等其他参数放入栈中

Java中使用HttpRequest调用RESTfull的DELETE方法接口提示:How to fix HTTP method DELETE doesn&#39;t support output

说明:无论是Spring框架还是Spring Boot的Feign形式的客户端,以下的解决方法都适用. 解决方法:直接升级JDK 1.8,这个问题是1.7的BUG. 参考: https://salesforce.stackexchange.com/questions/34624/http-method-delete-doesnt-support-output https://salesforce.stackexchange.com/questions/66097/how-to-fix-http-

Java面向对象---方法递归调用

递归调用是一种特殊的调用形式,即方法自己调用自己 public int method(int num){ if(num==1){ return 1; } else { return num+method(num-1); } } 描述:main方法调用method(5),在method方法中参数不等于1,则再调用method(4),一直到参数等于1,则依次返回.在该例中,参数等于1时,将1返回给method(2),依次类推,当返回到method(5)时,再将从1加到5的和返回给main方法.

Java中socket接口调用

最近一个项目中接口通讯这一块主要是调用银联系统的socket接口,我方是客户端,即发送请求接收返回报文的一方.在贴代码之前,还是要了解一下关于socket的基础知识. Socket的基本概念 1.建立连接 当需要建立网络连接时,必须有一台机器运行一个程序,随时等候连接,而另一端的程序这对其发出连接请求.这一点同电话系统类似--必须有一方拨打电话,而另一方必须等候电话连通. 建立连接的过程为: (1)先在服务器端生成一个ServerSocket实例对象,随时监听客户端的连接请求. (2)当客户端需

Java中方法递归与File例题

1.有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问第二十个月的兔子对数为多少?(不死神兔)(1)数组方式 package 递归.作业; public class TuziArray { public static void main(String[] args) { //定义数组 int arr[] = new int[100]; int m=20; arr[1] = 1;//前两个月兔子数量为1只 arr[2] = 1; arr[3

Java中的递归思想

转自:https://www.cnblogs.com/xiaosen992608/p/4037682.html 递归: 递归的概念:方法自身调用自身则称为递归. 递归的分类: 间接递归:方法A调用方法B,方法B调用方法C,方法C调用方法A. 直接递归: 方法A调用方法A.(常用) 递归的注意事项: 递归一定要出口:结束递归的条件. 递归次数不要太多. 如果递归不结束,则会报错. java.lang.StackOverflowError: 栈内存溢出错误 递归会内存溢出隐患的原因: 方法不停地进栈

Java中的递归原理分析

解释:程序调用自身的编程技巧叫做递归. 程序调用自身的编程技巧称为递归( recursion).递归做为一种算法在程序设计语言中广泛应用. 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量.递归的能力在于用有限的语句来定义对象的无限集合. 递归的三个条件: 边界条件 递归前进段 递归返回段 当边界条件不满足时,递归前进:当