00:00
在之前的练习当中,我们引出了两个概念,叫做颗粒化和必包,那对于函数式编程语言来说,其实必包是一非常重要的一个概念,可以说是一个标配啊,就是只要是函数式编程语言,那一定是支持B包的啊,当然对应来讲,如果是非函数式编程语言呢,可能对于B包的支持就没有那么好,比如说像Java啊,本来它是不支持B包的啊,或者如果要想实现的话,那就得用接口加上内部类啊类似的东西去实现,比较复杂,那直到抓了1.8之后。借鉴了scla里边的一些语法特性,引入了拉姆的表达式啊,从这个时候开始才是真正的支持B包了,所以说B包对于scla这门语言,它的函数式编程特性而言非常的重要,是一个重点,也是一个难点啊,那大家如果要是对于函数式编程比较感兴趣的话,B包和颗粒化这一部分是必须要掌握的,那首先我们先来看一下B包的概念,什么叫B包呢?我们说如果一个函数它访问到了。
01:07
它外部的一些局部变量的话,那么这个函数和它所处的整个环境,包括它访问到的那些局部变量的值打包在一起,整个的这个包保存起来就叫做B包。这是它的一个概念啊,当然如果直接看这个概念的话,可能还是有点空洞,比较抽象,我们还是结合之前练习里边的这个例子来给大家做一个具体的分析。比如说在这个例子里边,我们是实现了这样一个函数的嵌套啊,就是外层函数呢,是以内层函数作为它的返回值的fun啊,它的返回值就是里边的这个F1 f1的返回值呢,是它里边的F2 f2这里边真正的有一个判断返回出false的一个布尔类型的值,那这里边整体来看的话没什么问题,但是我们其实发现了内层函数F2。
02:00
他其实在判断的时候用到的并不仅仅只是自己内自己的参数,或者自己的内部的局部变量,它还用到了I和S,这两个其实是在外部去定义声明出来的,一个是funk的参数,一个是F1的参数。那我们自然就想到了,呃,从作用域的角度来讲,你这里声明的时候能访问到这个是可以的,但是调用的时候,他还真的能够找到之前外部的这个I和S吗?我们一步一步来具体做一个分析,首先funk,然后传一个int类型的参数,放K0,这是得到了一个啊,得到了一个函数的返回值,相当于得到了一个F1,那其实大家知道只要做了这一层调用fo c其实就已经执行结束了。就直接把这个F1返回了,那大家会想到把F1已经返回了,放已经调用结束了,那这个I是不是作为一个局部变量就已经被释放掉了呀。
03:02
呃,接下来其实就访问不到了,那同样F一再传第二个参数,F1调用结束之后,那接下来我们得到的是一个函数是F2,那F1呢,也已经调用结束,S作为局部变量是不是也应该释放掉了呀?那最后一步我们这里要调用F2的时候,还要访问I和S,那还拿得到吗?正常来看的话,按照我们本身对于语言的理解,这个时候其实真的是已经拿不到了,所以我们这样的实现其实不对。那如果想要让F2里面能够访问到外部的变量,应该怎么办呢?诶,大家能想到的一个简单的方法就是我把它保存下来嘛,那怎么保存呢?我可以在外边把把这个I和S都作为参数传到F2里边,F2就变成了三个参数,I和S都放在里边,哎,那这样的话,即使是fun和F1都已经执行完毕,都已经释放掉了,但是F2呢,我还把参数保存下来了,所以接下来调用那就完全没有问题了。
04:08
但是这种方式不好啊,那大家自然能想到你这样的话,层层传递,把每一层的参数全部都保存下来,那显然会大量的耗费我们的内存资源,如果说我们这个嵌套的层级比较深的话,那最后我们对于内存的消耗可能是非常非常大的,这不是一个很好的实现方式,那怎么样实现更好呢?有没有更好的方式呢?当然有,就是我们所说的B包啊,那B包听起来比较高大上,其实也非常的简单,它的思路也是就是要为我们内层函数它所依赖的那些变量呢,要把它保存下来,保证我们到时候调用的时候能够找到,那怎么保存呢?它就是打了一个包,就是说我们有一个内层函数F2,它依赖于外部的I和S,那么。
05:00
我们在处理这个F2的时候呢,就要把它和它依赖的这个局部变量直接都保存下来,保存在一起打成一个包,哎,那这样的话,外边的F1和放它已经调用结束,那它直接退出,然后释放所有的局部变量啊,全部清除掉没关系。F2这边已经打包保存好了,我们就相当于延长了外部局部变量的生命周期,使得后边内存,呃,后边的内存函数做调用的时候还可以访问得到它。这是这就是B包的一个最主要的特性,那为了方便大家更深刻的理解,我们可以基于JVM的内存结构再给大家做一个具体的分析啊,我这里可以给大家画一个示意图。呃,这里我们并没有详细的给大家讲GVM,就是在skyla语言里边啊,对于这个JVM底层内存的一些影响和这个变化,其实整体来讲就是它跟Java一样,只不过呢,这里边涉及到了一个对于B包的支持,那那这里我们就要单独的来说一下了啊,我们都知道GM的运行时内存结构啊,主要是这么几大块。
06:14
一部分是。CM站sack。然后一部分是对应内存hip。啊,另外当然还有一部分是所谓的这个方法区啊,方法区我们这里边不涉及到,主要它是存放我们这个运行时的常量池嘛,啊那hip呢,堆主要是存放,哎,我们使用到的一些对象啊,所有的这个对象都是放在堆里面的,而这个step呢。Stack里边存放的就是我们所有的局部变量,还有操作数啊,所有的这个都是保存在这个站里边的,那所以接下来呃,我们我们知道。堆本身是线程之间是共享的,而站呢,线程是独享的,所以在一个线程里边啊,我们在这个。
07:07
这个它对应的就有一个GVM站啊,对对应的这样一个占空间,那么在一个线程里边,每当调用一个方法的时候,我们这里边调用它里边某一个方法,或者说我们这里面的某一个函数的时候啊,就会创建一个战针啊,那么这个战针会保存在当前现城的这个。空间里面你调用一个函数,用一个方法会把它压进来,哎,那所以之前我们比方说有这个funk。后边可能有这个F1有F2。那就是每调用一个,我们都会把它压入到站里边来,像之前我们理解的这个过程呢,因为它是依次调用,所以它其实是先调用funk,那么在执行的过程当中,我们用到的那些操作数,用到的局部变量,哎,所有的都要压,压入到这个当前的这个战争里面来,我们当前用到这个战争里边最主要的保存的就是局部变量表,还有操作数站啊,以及返回地址之类的一些东西嘛,所以这些都要压进来,然后等这个放个调用结束呢。
08:18
当前的战争就应该出战了,哎,所有的所有的内容就不再保存,就清空了,所以这里边大家会看到我画的时候是把这三个都好像是都都压占压进来了,其实呢,呃,其实是funk压进来,然后funk就弹战弹出来了,然后呢才是F1压进来,压进来之后又弹战弹出来了,所以等到F2压进来的时候,F1和funk他们保存的所有的局部变量都已经没有了。所以呃,正常情况下啊,这个时候我们再去做F2调用的时候,那就什么东西都拿不到,不能访问了,那这个时候怎么办呢?诶,我们在这个时候scalera做操作的时候呢,我们底层是它是一个完全面对对象的语言,所有的函数在底层也是一个对象实例,哎之前大家看到我们直接如果打印一个函数的话,会发现它其实是一个引用,一个对象的引用,那对应的这一个对象实例,那应该放在哪里呢?当然是放在堆内存里边。
09:19
所以现在呢,在sky skyla的这个呃特征里边,我们是。调用一个函数的时候,相当于是创建了它的一个对象实例,放对应的这个对象实例就放到了对内存里边,然后在这个对象实例里边呢,把相关的它所依赖的外部的环境和局部变量打包保存在这个对象实例里边。所以这个东西,这就是我们真正意义上的壁包保存的实体。那大家就知道了,Hep这一部分的内容,它不会因为当前线程的呃,当前方法的退出,当前这个函数调用执行的结束而把这个清掉,它的清除是依赖于我们JC机制的嘛,所以接下来啊,那后边这个F1F放执行完毕了,F1执行完毕了,F2调用的时候呢,当前这个占空间已经没有相关的内容了,但是在hip这个B包里边,所有依赖的东西都还保存在里面啊,所以这样的话,我们就不会出现这个呃依赖的局部变量丢失的情况了,大家可以深入的把这个原理再做一个理解,那除了B包这一部分之外别的内容,其实呃,Skyla里边的底层JVM的这个内存机制跟Java是完全一样的,我们就不再给大家详细展开了。
10:44
大家可以把这个B包根据这个内存底层的实现啊,理解的再做一个深入的理解。
我来说两句