一、首先我们讲讲递归

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

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

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

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

3、因为上边2和3两个特点,所以递归调用最大的诟病就是开销巨大,栈帧和堆一起爆掉,俗称内存溢出。

1.一个误区,不是因为调用自身而开销巨大,而是嵌套加上轻易就能无数次调用,使得递归可以很容易开销巨大,既然会导致内存溢出,那肯定要想办法了,方法很简单,那就是尾递归优化。

二、尾递归优化

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

2、尾递归优化其实包括两个东西:a 尾递归的形式 b 编译器对尾递归的优化

1、尾递归的形式

1.尾递归其实只是一种对递归的特殊写法,这种写法原本并不会带来跟递归不一样的影响,它只是写法不一样而已,写成这样不会有任何优化效果,该爆的栈和帧都还会爆

2. 具体不一样在哪里

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

3. 要求很简单,就一条,但是有一些常见的误区

1.这个f(n)外不能加其他东西,因为这就不是最后一件事了,值返回来后还要再干点其他的活,变量空间还需要保留

1.比如如果有返回值的,你不能:乘个常数return 3f(n);乘个n return n*f(n);甚至是f(n)+f(n-1)

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

2、编译器对尾递归的优化

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

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

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

3.为什么写成尾递归的形式,编译器就能优化了?或者说【编译器对尾递归的优化】的一些深层思想

1、说是深层思想,其实也是因为正好编译器其实在这里没做什么复杂的事,所以很简单

2、由于这两方面的原因,尾递归优化得以实现,而且效果很好

1.因为在递归用自身的时候,这一层函数已经没有要做的事情了,虽然被递归用的函数是在当前的函数里,但是他们之间的关系已经在传参的时候了断了,也就是这一层函数的所有变量什么的都不会再被用到了,所以当前函数虽然没有执行完,不能弹出栈,但它确实已经可以出栈了,这是一方面

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

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

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

举例说明

没有使用尾递归

def recsum(x):
if x == 1:
return x
else:
return x + recsum(x - 1)

使用了尾递归

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

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

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

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

     1.首先我们需要谈一下内存机制,这里我们需要了解内存机制的两个部分:栈和堆,下边虽然是在说java,但是C也是差不多的

1.在java中,jvm中的栈记录了线程的方法调用,每个线程拥有一个栈,在某个线程的运行过程中,如果有新的方法调用,那么该线程对应的栈就会增加一个存储单元,即栈帧(frame)。在frame中,保存有该方法调用的参数,局部变量和返回地址

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

2.然后由栈和堆的空间管理方式的不同,引出垃圾回收的概念

1.当被调用方法运行结束时,该方法对应的帧将被删除,参数和局部变量所占据的空间也随之释放,线程回到原方法,继续执行,当所有的栈都清空时,程序也随之运行结束,

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

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

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

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

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

     1.他们最本质的区别是,尾递归优化解决的是内存溢出的问题,而垃圾回收解决的是内存泄漏的问题

1.内存泄漏:指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用。

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

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

2.自动垃圾回收机制的特点是:

1.解决了所有情况下的内存泄漏的问题,但还可以由于其他原因内存溢出

2.针对内存中的堆空间

3.正在运行的方法中的堆中的对象是不会被管理的,因为还有引用(栈帧没有被清空)

1.一般简单的自动垃圾回收机制是采用引用计数的机制,每个对象包含一个计数器,当有新的指向该对象的引用时,计数器加1,当引用移除时,计数器减1,当计数器为0时,认为该对象可以进行垃圾回收。

3.与之相对,尾递归优化的特点是:

         1.优化了递归调用时的内存溢出问题

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

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

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

Java中谈尾递归--尾递归和垃圾回收的比较的更多相关文章

  1. 在Java中谈尾递归--尾递归和垃圾回收的比较(转载)

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

  2. 在Java中谈尾递归--尾递归和垃圾回收的比较

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

  3. 深入理解JAVA虚拟机之JVM性能篇---垃圾回收

    一.基本垃圾回收算法 1. 判断对象是否需要回收的方法(如何判断垃圾): 1) 引用计数(Reference Counting)  对象增加一个引用,即增加一个计数,删除一个引用则减少一个计数.垃圾回 ...

  4. (转)《深入理解java虚拟机》学习笔记3——垃圾回收算法

    Java虚拟机的内存区域中,程序计数器.虚拟机栈和本地方法栈三个区域是线程私有的,随线程生而生,随线程灭而灭:栈中的栈帧随着方法的进入和退出而进行入栈和出栈操作,每个栈帧中分配多少内存基本上是在类结构 ...

  5. Java进阶之内存管理与垃圾回收

    Java是在JVM所虚拟出的内存环境中运行的.内存分为栈(stack)和堆(heap)两部分.我们将分别考察这两个区域. 栈 在Java中,JVM中的栈记录了线程的方法调用.每个线程拥有一个栈.在某个 ...

  6. 初步探究java中程序退出、GC垃圾回收时,socket tcp连接的行为

    初步探究java中程序退出.GC垃圾回收时,socket tcp连接的行为 今天在项目开发中需要用到socket tcp连接相关(作为tcp客户端),在思考中发觉需要理清socket主动.被动关闭时发 ...

  7. 《Beginning Java 7》 - 8 - Collecting Garbage 垃圾回收

    Java 垃圾回收机制原理: Java 语言使用 garbage collector 来进行垃圾回收.它是允许在后台的代码,间或地检查没有引用的对象(unreferenced object).发现后, ...

  8. (转载)JVM中的内存模型与垃圾回收

    转载自微信公众号:Java高级架构(Java-jiagou)-----看完这篇文章,我奶奶都知道JVM中的内存模型与垃圾回收了! 六.内存模型 6.1  内存模型与运行时数据区 Java虚拟机在执行J ...

  9. javascript中的内存管理和垃圾回收

    前面的话 不管什么程序语言,内存生命周期基本是一致的:首先,分配需要的内存:然后,使用分配到的内存:最后,释放其内存.而对于第三个步骤,何时释放内存及释放哪些变量的内存,则需要使用垃圾回收机制.本文将 ...

随机推荐

  1. Ubuntu下解压压缩文件

    1.ZIP解压    ZIP因为它的跨平台使用优点,是目前使用率最高的一种压缩方式,但是它的压缩率相比较tar.gz和tar.gz2来讲,却要低很多.    压缩命令:zip -r archive_n ...

  2. 一个.net的程序员如何转到java的?

    先说明,大佬请忽略我这篇文章, 我是一个做了5年的纯种C#开发人,  我在此仅记录一下我转java的过程.都知道, java是开源的,所以它的开发工具贼多,不像.net, 直接地表最强的IDE. 像现 ...

  3. day02(编程语言,解释器,环境变量,执行方式,pycharm,pip,变量三大组成)

      上节课复习: 重点: 1,进制转换:二进制与十六进制 2,内存分布:栈区 与 堆区 10101001110111 => 2a77 abf1 => 1010101111110001 计算 ...

  4. php中header函数参数的Cache-control的使用方法

    网页的缓存是由HTTP消息头中的“Cache-control”来控制的,常见的取值有private.no-cache.max-age.must-revalidate等,默认为private.其作用根据 ...

  5. 使用axios post 提交数据,后台获取不到

    https://www.cnblogs.com/yiyi17/p/9409249.html(copy https://www.cnblogs.com/loveyaxin/p/8385694.html( ...

  6. mysql千万级数据优化查询

    我们在做一个项目,一个网站或一个app时,用户量巨增,当使用的mysql数据库中的表数据达到千万级时,可以从以下方面考滤优化: 1.在设计数据库表的时候就要考虑到优化 2.查询sql语句上的优化 3. ...

  7. Word Representations 词向量

    常用的词向量方法word2vec. 一.Word2vec 1.参考资料: 1.1) 总览 https://zhuanlan.zhihu.com/p/26306795 1.2) 基础篇:  深度学习wo ...

  8. Lodop简答问答大全

    其他相关简短问答:Lodop简短问答客户反馈篇 及排查步骤 及注册相关,Lodop某个电脑打印内容大小有问题,LODOP超文本简短问答和相关内容,LODOP.C-Lodop简短排查语句.Lodop.c ...

  9. redis - Sentinel 和 cluster

    redis哨兵集群 引入 上回说到redis主从同步时,master(主库)如果宕机了怎么解决... 我给出了一个手动解决的办法! 但实际上,如果你配置了Sentinel,它能自动发现master宕机 ...

  10. Magento2 可配置产品解决SKU流程

    选择可配置产品: 填写必填信息与库存 创建配置 执行四步后完成创建:4.1:选择需要的规格属性: 4.2:选择组合需要的属性值:4.3:根据您的选择,将创建3个新产品.使用此步骤自定义新产品的图像和价 ...