我不是故意在JAVA中谈尾递归的,因为在JAVA中谈尾递归真的是要绕好几个弯,只是我确实只有JAVA学得比较好,虽然确实C是在学校学过还考了90+,真学得没自学的JAVA好

不过也是因为要绕几个弯,所以才会有有意思的东西可写,另外还有我发现把尾递归如果跟JAVA中的GC比对一下,也颇有一些妙处(发现还没有人特地比较过)

(不过后来边写边整理思路,写出来又是另一个样子了)

一、首先我们讲讲递归

递归的本质是,某个方法中调用了自身。本质还是调用一个方法,只是这个方法正好是自身而已

递归因为是在自身中调用自身,所以会带来以下三个显著特点:调用的是同一个方法

因为1,所以只需要写一个方法,就可以让你轻松调用无数次(不用一个个写,你定个n就能有n个方法),所以调用的方法数可能非常巨大

在自身中调用自身,是嵌套调用(栈帧无法回收,开销巨大)

因为上面2和3两个特点,所以递归调用最大的诟病就是开销巨大,栈帧和堆一起爆掉,俗称内存溢出泄露一个误区,不是因为调用自身而开销巨大,而是嵌套加上轻易就能无数次调用,使得递归可以很容易开销巨大

既然会导致内存溢出泄露如此,那肯定要想办法了,方法很简单,那就是尾递归优化

二、尾递归优化

尾递归优化是利用上面的第一个特点“调用同一个方法”来进行优化的

尾递归优化其实包括两个东西:1)尾递归的形式;2)编译器对尾递归的优化尾递归的形式尾递归其实只是一种对递归的特殊写法,这种写法原本并不会带来跟递归不一样的影响,它只是写法不一样而已,写成这样不会有任何优化效果,该爆的栈和帧都还会爆

具体不一样在哪里前面说了,递归的本质是某个方法调用了自身,尾递归这种形式就要求:某个方法调用自身这件事,一定是该方法做的最后一件事(所以当有需要返回值的时候会是return f(n),没有返回的话就直接是f(n)了)

要求很简单,就一条,但是有一些常见的误区这个f(n)外不能加其他东西,因为这就不是最后一件事了,值返回来后还要再干点其他的活,变量空间还需要保留比如如果有返回值的,你不能:乘个常数 return 3f(n);乘个n return n*f(n);甚至是 f(n)+f(n-1)

另外,使用return的尾递归还跟函数式编程有一点关系

编译器对尾递归的优化上面说了,你光手动写成尾递归的形式,并没有什么卵用,要实现优化,还需要编译器中加入了对尾递归优化的机制

有了这个机制,编译的时候,就会自动利用上面的特点一来进行优化

具体是怎么优化的:简单说就是重复利用同一个栈帧,不仅不用释放上一个,连下一个新的都不用开,效率非常高(有人做实验,这个比递推比迭代都要效率高)

为什么写成尾递归的形式,编译器就能优化了?或者说【编译器对尾递归的优化】的一些深层思想说是深层思想,其实也是因为正好编译器其实在这里没做什么复杂的事,所以很简单

由于这两方面的原因,尾递归优化得以实现,而且效果很好因为在递归调用自身的时候,这一层函数已经没有要做的事情了,虽然被递归调用的函数是在当前的函数里,但是他们之间的关系已经在传参的时候了断了,也就是这一层函数的所有变量什么的都不会再被用到了,所以当前函数虽然没有执行完,不能弹出栈,但它确实已经可以出栈了,这是一方面

另一方面,正因为调用的是自身,所以需要的存储空间是一毛一样的,那干脆重新刷新这些空间给下一层利用就好了,不用销毁再另开空间

有人对写成尾递归形式的说法是【为了告诉编译器这块要尾递归】,这种说法可能会导致误解,因为不是只告诉编译器就行,而是你需要做优化的前半部分,之后编译器做后半部分

所以总结:为了解决递归的开销大问题,使用尾递归优化,具体分两步:1)你把递归调用的形式写成尾递归的形式;2)编译器碰到尾递归,自动按照某种特定的方式进行优化编译

举例:

(没有使用尾递归的形式)

defrecsum(x):if x == 1:returnxelse:return x + recsum(x - 1)

(使用尾递归的形式)

def tailrecsum(x, running_total=0):if x ==0:returnrunning_totalelse:return tailrecsum(x - 1, running_total + x)

但不是所有语言的编译器都做了尾递归优化。比如C实现了,JAVA没有去实现

说到这里你很容易联想到JAVA中的自动垃圾回收机制,同是处理内存问题的机制,尾递归优化跟垃圾回收是不是有什么关系,这是不是就是JAVA不实现尾递归优化的原因?

三、所以下面要讲一下垃圾回收(GC)

首先我们需要谈一下内存机制,这里我们需要了解内存机制的两个部分:栈和堆。下面虽然是在说JAVA,但是C也是差不多的在Java中, JVM中的栈记录了线程的方法调用。每个线程拥有一个栈。在某个线程的运行过程中, 如果有新的方法调用,那么该线程对应的栈就会增加一个存储单元,即栈帧 (frame)。在frame 中,保存有该方法调用的参数、局部变量和返回地址

Java的参数和局部变量只能是 基本类型 的变量(比如 int),或者对象的引用(reference) 。因此,在栈中,只保存有基本类型的变量和对象引用。而引用所指向的对象保存在堆中。

然后由栈和堆的空间管理方式的不同,引出垃圾回收的概念当被调用方法运行结束时,该方法对应的帧将被删除,参数和局部变量所占据的空间也随之释放。线程回到原方法,继续执行。当所有的栈都清空时,程序也随之运行结束。

如上所述,栈 (stack)可以自己照顾自己。但堆必须要小心对待。堆是 JVM中一块可自由分配给对象的区域。当我们谈论垃圾回收 (garbage collection) 时,我们主要回收堆(heap)的空间。

Java的普通对象存活在堆中。与栈不同,堆的空间不会随着方法调用结束而清空(即使它在栈上的引用已经被清空了)(也不知道为什么不直接同步清空)。因此,在某个方法中创建的对象,可以在方法调用结束之后,继续存在于堆中。这带来的一个问题是,如果我们不断的创建新的对象,内存空间将最终消耗殆尽。

如果没有垃圾回收机制的话,你就需要手动地显式分配及释放内存,如果你忘了去释放内存,那么这块内存就无法重用了(不管是什么局部变量还是其他的什么)。这块内存被占有了却没被使用,这种场景被称之为内存泄露

所以不管是C还是JAVA,最原始的情况,都是需要手动释放堆中的对象,C到现在也是这样,所以你经常需要考虑对象的生存周期,但是JAVA则引入了一个自动垃圾回收的机制,它能智能地释放那些被判定已经没有用的对象

四、现在我们就可以比较一下尾递归优化和垃圾回收了

他们最本质的区别是,尾递归优化解决的是内存溢出的问题,而垃圾回收解决的是内存泄露的问题内存泄露:指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用。

内存溢出:指程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。

从定义上可以看出内存泄露是内存溢出的一种诱因,不是唯一因素。

自动垃圾回收机制的特点是:解决了所有情况下的内存泄露的问题,但还可以由于其他原因内存溢出

针对内存中的堆空间

正在运行的方法中的堆中的对象是不会被管理的,因为还有引用(栈帧没有被清空)一般简单的自动垃圾回收机制是采用 引用计数 (reference counting)的机制。每个对象包含一个计数器。当有新的指向该对象的引用时,计数器加 1。当引用移除时,计数器减 1,当计数器为0时,认为该对象可以进行垃圾回收

与之相对,尾递归优化的特点是:优化了递归调用时的内存溢出问题

针对内存中的堆空间和栈空间

只在递归调用的时候使用,而且只能对于写成尾递归形式的递归进行优化

正在运行的方法的堆和栈空间正是优化的目标

最后可以解答一下前头提出的问题

通过比较可以发现尾递归和GC是完全不一样的,JAVA不会是因为有GC所以不需要尾递归优化。那为什么呢,我看到有的说法是:JAVA编写组不实现尾递归优化是觉得麻烦又没有太大的必要,就懒得实现了(原话是:在日程表上,但是非常靠后),官方的建议是不使用递归,而是使用while循环,迭代,递推

参考资料:

http://it.deepinmind.com/jvm/2014/04/16/tail-call-optimization-and-java.html

http://book.51cto.com/art/201212/370096.htm

java递归优化_在Java中谈尾递归--尾递归和垃圾回收的比较相关推荐

  1. java游戏优化_用 JAVA 开发游戏连连看(之六)优化:让程序运行更稳定、更高...

    之六)优化:让程序运行更稳定.更高效 改善游戏的合理性 到目前为止,我们的游戏基本上算是完成了,为了使程序更合理,我们还需要将整个程序从头再理一遍,看看有没有改进的地方. 首先,在变量的使用上,由于很 ...

  2. java jmap 分析_利用java虚拟机的工具jmap分析java内存情况

    有时候碰到性能问题,比如一个java application出现out of memory,出现内存泄漏的情况,再去修改bug可能会变得异常复杂,利用工具去分析整个java application 内 ...

  3. JVM原理(Java代码编译和执行的整个过程+JVM内存管理及垃圾回收机制)

    转载注明出处: http://blog.csdn.net/cutesource/article/details/5904501 JVM工作原理和特点主要是指操作系统装入JVM是通过jdk中Java.e ...

  4. java coin介绍_代码示例中的Java 7:Project Coin

    java coin介绍 该博客通过代码示例介绍了一些新的Java 7功能,这些项目在Project Coin一词下进行了概述. Project Coin的目标是向JDK 7添加一组小的语言更改.这些更 ...

  5. java 异常对象_在java中的异常处理中的异常对象是什么

    展开全部 Exception类以及他的子类 的一个实例对象 比如32313133353236313431303231363533e58685e5aeb931333264633563 常见异常 1. j ...

  6. java 7 锁优化_自Java 6/Java 7开始,Java虚拟机对内部锁的实现进行了一些优化。这些优化主要包括锁消除(Lock Elision)、锁粗化(Lock Coarse...

    自Java 6/Java 7开始,Java虚拟机对内部锁的实现进行了一些优化.这些优化主要包括锁消除(Lock Elision).锁粗化(Lock Coarsening).偏向锁(Biased Loc ...

  7. java实现分而治之_并发编程中一种经典的分而治之的思想!!

    写在前面 在JDK中,提供了这样一种功能:它能够将复杂的逻辑拆分成一个个简单的逻辑来并行执行,待每个并行执行的逻辑执行完成后,再将各个结果进行汇总,得出最终的结果数据.有点像Hadoop中的MapRe ...

  8. java final 类_在Java中,final修饰的类有什么特点

    展开全部 关于Java中的32313133353236313431303231363533e4b893e5b19e31333264663736final(2010-09-09 14:19:48)转载▼ ...

  9. java finalize逃脱_关于Java中的finalize()方法

    java提供finalize()方法,垃圾回收器准备释放内存的时候,会先调用finalize(). (1).对象不一定会被回收. (2).垃圾回收不是析构函数. (3).垃圾回收只与内存有关. (4) ...

最新文章

  1. 图灵5周年系列活动之“有奖DEBUG”
  2. 收藏 | 数据智能与计算机图形学领域2019推荐论文列表(附链接)
  3. [官方摘要]Setup And Configuration memcached with Tomcat
  4. 第9章 数据字典(选项)管理
  5. tcp协议缓冲区溢出_关于TCP 粘包拆包,你了解吗?
  6. xp工作组计算机打不开,XP“网上邻居”、“查看工作组计算机”打不开及无法访问局...
  7. ResourceLoader
  8. Angular Remove me测试应用的工作原理
  9. Visual Studio 2019 16.1发布,更快更高效
  10. 第一百零九期:双十一光棍节调试一个商城必备功能,Java Springboot开源秒杀系统
  11. 漫画 | 让设计师崩溃的十个瞬间
  12. bt磁力种子与php文件,BT,种子和磁力链接
  13. 遗传算法原理及应用一(编码、适应度函数与选择算子)
  14. 计算机组成原理与接口技术笔记
  15. 汇编常用DOS命令调用
  16. 为什么要学习数据库以及数据库的选择
  17. 决策树- 随机森林/GBDT/XGBoost
  18. error2203matlab,小编详解Win10安装MATLAB软件提示Internal error 2203的解决方法
  19. ERP在企业财务管理中的应用
  20. CSDN App产品分析报告

热门文章

  1. 【Flink】RuntimeException: Row arity of from does not match serializers
  2. 【kafka】记一次线上kafka一直rebalance故障 消费慢 数据积压
  3. 【SpringBoot】SpringBoot+druid+Myibatis集成
  4. 仿造小红书页面代码html,jQuery仿小红书登录页,背景图垂直循环滚动登录页,向上循环滚动的动画,实现一张背景图片的无缝向上循环js滚动...
  5. php家检乘除,php通用检测函数集(转)_php
  6. clickhouse 同步mysql_ClickHouse和他的朋友们(11)MySQL实时复制之GTID模式
  7. php7 viewmodel,ViewModel浅析
  8. 多线程环境下,HashMap为什么会出现死循环?
  9. 企业最喜欢招聘什么样的Java程序员?谈谈我的看法
  10. Kafka Manager界面添加Partition