00:00
接下来啊,咱们来看这个垃圾清除阶段的第二个算法,哎,咱们呢叫做复制算法,好看一下,首先呢,我们也是来看一下关于这个算法的一个背景是吧,为了解决标记清除算法在垃圾收集效率方面的缺陷,哎,这个记得咱们刚才呢提到过,说这个算法呢,执行效率呢不算高,对吧,他需要呢,进行过两次的这个便利操作才行,标记一次清除一次对吧?行,那么这个ML啊,Missy说这个人啊,在1963年呢,发表了一篇著名的论文,说呢叫使用双存储区的list语言的垃圾收集器。那这个就是他这个论文的一个名字行,那么在这个论文当中啊,他就描述了这样的一个算法啊,那这个算法呢,后人就把它称为呢,叫做复制算法。那他呢,也成功的把这种算法呢,引入到了这个类似语言的一个实践版本当中,OK,这是六三年相当于我们这个算法呢发明出来的,那这个算法的核心思想是什么呢?我们看一下说呀,将活着的这个内存空间分成两块,说每次啊只使用其中的一块区域。
01:04
在进行垃圾回收的时候呢,将正在使用中的内存中的存活对象复制到未被使用的内存块当中,那之后啊,咱们再去清除正在使用的这个内存块中的所有对象。诶,那后边呢,你要再操作,那就交换两个内存对象的这个角色来完成垃圾回收就行了。行,那这块呢,我们通过这个图呢,来进行一个描述啊,其实呢比较好理解,这呢就是咱们所谓这个叫根节点对吧,那我们这块呢,去进行查找的时候,通过根节点呢,进行这个遍历,那凡是你能够便利到的,那自然而然它就是可达对象,我们通过这个根节点呢,找到了这个区域的对象,然后这个又找到这个对象,然后一旦找到它,我们也不用标记了,直接呢就把这个对象呢,放到另一块跟它大小一样的这个内存空间当中,直接呢就复制过来,注意复制啊,复制这个复制不是那个,这个只是取一个他的地址啊,整个把这个对象的这个实体复制一份。然后呢,你这个对象呢,又引用了另外一个对象啊,把这个对象呢,也不用标记了,也是直接复制过来,然后还有其他的这个根节点,同样的道理都这样去处理,那那你复制的时候呢,我们就可以让这个空间呢,就是连续的一个挨一个紧密排列了。
02:14
那就紧密排列的这样一个形式,那剩下的这块区域呢,你看它就始终还是空的,然后呢,你没有呃通过这个根节点呢,直接或间接的呃,给它关联到的这个对象,像我们这里边这个黑色区域,那自然而然的它就是不可达的对象,对吧?OK,那当我们把这个可达的这个对象呢,复制过来以后呢,整个这个A区啊,对于咱们来讲就没有用了,那你整个把这个A区呢,就整接整个呢给它销毁掉就行,对吧?然后我们这个B区呢,就这样子,那我们下次呢,你放数据你可以,呃接着往这个这块放,如果是需要这个B区呢,再进行垃圾回收的时候呢,我们再重复一下刚才A环节。就是A这个区域当时的这个操作,你把这个B区当中所有呢,可达的这个对象呢,你再复制到这个A区当中,所以AB区呢,他们是交换着这样去使用。
03:03
哎,是这样一个逻辑啊,那一说到这儿的话呢,大家可能会想到咱们前边。咱们前边是不是讲到新生代当中对象的内存一个叫回收和分配的时候呢,实际上咱们就使用了这个复制算法,就是针对于咱们说的叫survivor区,就是两个幸存者区,或者呢,咱们也称为呢叫from区和to区,当然说呢谁空谁是to是吧,说明他们身份呢有过这个交换。回忆一下,咱们说呢,数据呢,通过伊甸园区,首先呢,比如说我们进行一次央JC,把数据呢,存活的就放到我们这个S0去了,然后下一次呢,你伊甸园区满的时候呢,我们再进行一个样JC的时候,那么S0区的这个对象呢,我们也进行回收,回收的时候呢,说存活的咱们就放到这个S1了。然后同时呢,这一年区的存货对象又放到S1,然后接下来这个S0就被清空掉了,然后当我们下次再进行这个样GC的时候呢,S0的这个存货对象是不是又放S里,S1里边的存货对象又放到S0里了,所以针对于S0S1这两个区域,他们使用的实际上就是咱们现在要讲的这个叫复制算法。
04:06
诶,就是这个复制算法,OK,行,这是我们说明跟咱们现有的一个知识点呢,咱们关联起来,这是一个,那另外一个呢,就是咱们如果从一个生活中去举例子的话,呃,刚才呢,是不是也跟大家说过,那个机器人在这个餐厅里边,他专门来打扫这个已经被人使用过的这个餐巾纸的这个事儿,对吧。啊,那这时候我们还可以呢,举这样一个例子,现在的话呢,咱们这个餐厅不是比如说原来这个餐厅这么大,咱们呢,上一个例子呢,叫标记清除算法,现在的话呢,我们把这个餐厅啊,得给它劈成两半了。那很显然,咱们现在在这个餐厅里边就餐的人数呢,就是人数的上限,那瞬间呢,是不是就减半了,诶这呢也是咱们说复制算法它的一个缺点啊,那么我们现在呢这呢,比如说称为这个左西,这个咱们称为叫西区,这个呢叫东区,现在呢,我们首先呢,让这些客人啊,都在这个西区这块去就餐。先在这块去就餐,行,那么就餐的过程当中啊,这个有些呢,这些客人呢,手里边拿着自己的餐巾纸,正常咱们认为就是你还正在被使用的,就是可答对象,那有些呢,把这个餐巾纸呢,他就扔到桌子上或者扔到地上了,这个呢就成为这个,呃,我们认为的就是不再被使用的这些,呃,这个对象了,行,然后现在呢,这个机器人,他说这个发广播说呢,这个所有的客人啊,说你们呢,请到这个东区,请到这个东区就餐,那么这些客人呢,就拿着你手里边这个没有使用完的这个餐巾纸,你就直接呢,就奔到这个东区啊就全过来,那过来以后呢,这个机器人呢,就直接走到走到这个西区当中,就直接呢开始去回收就可以了。
05:35
因为呢,你这个客人呢,带走的这个餐巾纸呢,我们就认为他不是垃圾了,你没带走的和扔到地上的,扔到桌子上呢,我们认为它就是垃圾,机器人呢,直接过去剩着什么餐巾纸,他就完全的进行一个回收就可以了。那下一个环节呢,就是说如果你这个东区里边接下来又去使用,你再让这个客人呢,是不是从东区再到西区,然后机器人呢,再去回收这个东区就可以了,对吧?这呢就是我们说生活中类似的这样的一个例子。行,那么通过这个例子,包括咱们前面讲的这个SURVIVOR0区和预期之后呢,咱们看一下这个复制算法它的一个优缺点。
06:08
啊,它的一个优缺点,首先它的优点呢,也是非常明确的说呢,没有进行过标记和清除的这个过程,它的实现啊非常的简单,运行高效,这个呢是咱们复制算法最明显的一个特征就是运行高效,效率非常高。那效率非常高,然后复制过去以后呢,咱们还能够保证空间的一个连续性,不会出现呢这个碎片的问题,那当然你会发现呢,咱们放到必须当中可达的这个对象,你看呢,它这个内存呢,是紧密排列的,我们空闲的这个区域呢,是这你下次呢再放这个对象,直接呢,是不是从这个空闲区域放就行,那它呢,恰好对应的就是咱们前面讲到这个对象实例化的时候呢,哎,针对于复制算法,它呢,是不是接下来使用的这叫指针碰撞了就可以。对吧,哎,使用智能碰撞在我们这个B区这块再去分配数据就行,而我们的这个标记清楚算法呢,它只能够是不是使用咱们叫空闲列表的方式啊。
07:03
对吧,诶这呢跟咱们已有的这个知识呢,咱们又连到一起了,OK行这呢是我们说的它明显的这样的两个优点,哎这两个优点避免了碎片问题,那实际上我们也用不着呢,是不是进行所谓的碎片整理了,对吧?OK行,这呢是它这个优点,然后呢,这个缺点呢,大家看看。缺点啊,实际上也非常的明显。啊,这里边明显的就是说它需要两倍的内存空间,你想我们把这个原有的这个餐厅啊,你本来呢能容纳100个人,现在呢,你给大家分成两半了,说只能够容纳50人,这一部分呢,是不是完全的空闲起来啊,对吧,那就意味着我们这个内存空间呢,就相当于是始终有一部分呢是空闲状态。啊,这个时候呢,我们这个内存空间浪费上呢,是稍微大一些啊,这是这个问题,下一个呢,说对于这个JC,呃G1或者我们也称为呢叫g first,这个垃圾回收器呢,是咱们夏小章要讲的啊,也是咱们现在呢JDK里边默认的这个垃圾回收器,它呢是选择把咱们内存呢分成分成了大量的叫region啊这个分区了啊,那分成这个大量的这个region振以后呢,如果说你此时呢进行复制算法,而且呢,还不是一个移动啊,是一个复制的话呢,就意味着咱们这个这次呢需要维护的这个region振之间的引用关系也需要做一个调整。
08:15
那这时候呢,我们说不管是内存占用还是说时间开销上也不太小。啊,能理解这个事情吗?哎,大家想想,咱们现在是做一个复制操作,咱都知道啊,这是这个占空间,这是这个堆空间,现在对空间中这个对象呢,你要从这个区域呢,是不是给它要复制到另外一个区域当中,然后把这个区域呢给它清空掉,那就意味着我们占空间,这个引用呢,一开始指向的是它,现在呢,你复制到这儿了,我们这个引用是不是该指向它了,咱们需要维护的这个指针是不是要做一个调整啊?诶这呢,又涉及到咱们之前讲过的一个知识点,你看跟咱们有的这个知识呢,咱们都能连成片,就是咱们在讲这个对象的时候呢,这不提到过对象的这个具体引用的访问方式,一种呢,叫这种聚柄持的访问,一种呢,就是我们Java呢,采用的是这种方式。
09:00
嗯,这种方式,那么这种方式的好处呢,相对于我们说这个聚丙池呢,那咱们就不用维护这么多这个值呢,访问起来的话呢,你看这个还挺麻烦的,先到聚丙池里边找到你对应的这个聚柄,然后呢再去找这个对象,而我们这个使用这种直接的方式呢,就一步到位,直接就找到这对象了,但是缺点呢,就是我们这个对象呢,一旦它的位置要改变过,是不是我们这个引用地址要跟着变啊。这就是这个缺点,那这个对象的位置怎么会变呢?咱们现在使用这种复制算法。嗯,咱们现在使用的这种复制算法,它其实不就是导致我们这个对象的这个存储的位置是不是就变了,原来呢,在对空间中的这个A区,现在我们放到这个B区里了,复制过去了,这个呢清空了,那这样就导致这个地址要变,地址要变的话呢,对于我们说这个g first垃圾回收器来讲,它的这个region呢,哎,频繁的进行调整,我们呢,这个引用关系啊,包括与瑞之间他们互相调用,这个都得做一个调整,所以这块呢,开销也不算小,这呢是它的一个缺点。啊,这个大家呢,回回味一下行,然后这块呢,还有一个非常重要的问题给大家呢,要去强调的就是我们这个复制算法,你如果从这个优点上来看的话,你觉得它还是非常不错的,对吧?那么这个至于说这个空间占用的多了啊,说大不了那我们就多多多开辟一块空间,对吧,那个现在呢,我们说硬件呢,相对来说价格呢也不是特别高,我们更关注的还是这个时间上呢要快。
10:23
啊,时间上要快,所以说呢,我们如果你更看重它的优点的话呢,诶,那我们就可以考虑呢,说在很多场景下去用,但是这里边我们有一个特别需要注意的问题。啊,这个问题说完了才算是一个完整的,就是说在系统当中这个垃圾,垃圾对象如果要是比较多的话呀,就比较麻烦了。嗯,如果系统中的这个垃圾对象很多,复制算法呢,需要复制这个存活对象呢,是不是就很多,而我们这个要求的这个复制算法,这个存活对象最好还不要太大,或者说呢,比较低才行。哎,这个我们这一块好像不是特别完整啊,说如果系统中的垃圾对象很多,然后这个复制算法呢,就不太理想。
11:05
啊,这个我在这补一下啊,复制算法啊就。嗯,不会很理想。啊,因为呢,咱们复制算法呢,要需要复制的这个存活对象呢,数量通常并不会太大,或者说呢,比较低才行,大家能去理解这个问题吗?你想象一下,如果这个时候啊,咱们在A区当中,我这呢,这个非白色的,这不都是咱们所谓的这个对象,如果呢,我们通过这个,呃,根据点这块呢,开始去遍历,发现呢,哇,竟然所有的这些对象呢,就是你占用空间的这些对象全都活着,全都活着啊,你百分百的都复制到另外一个区域了。想想是不是很崩溃啊?那我们回收一遍垃圾,发现呢,什么垃圾也没回收掉,还平白无故的全都复制了一份,还把这个所有的引用地址呢,跟我们这个占空间呢,在对接的时候呢,把占空间的这个引用地址呢,也全部给它重置了一遍,那你这个时候的这个效率呢,显然是不是就很差了呀。
12:01
优点呢,也变成了缺点了,对吧。哎,过犹不及了,行,那通过这样的一个启示啊,那我们得到的结论是什么呀?就是咱们这个复制算法啊,咱那个复制算法呢,最好你应用在你这块区域呢,最后呢,我们要复制的这个对象并不太多,也就是说比如说这里边这个所有的对象呢,有100个,假设呢,诶咱们进行这个复制算法操作以后发现诶存活了就只有五个。那这个时候呢,我们认为就是比较理想的,就是你大部分的对象呢,都得死啊,用不着我去复制才行,诶你要这样一说的话呢,咱们就想到咱们前面讲到这个新生代的时候呢,提到过说呢,新生代中的对象啊,是不是绝大部分都是朝生夕死的呀。对吧,诶也就是说呢,新生代中的对象呢,它的死亡率是非常高的,那既然如此,所以说呢,咱们使用这个survivor区,哎,使用这个survivor区呢,咱们这个进行啊,使用这个复制算法呢,应该说是非常漂亮的。
13:04
啊,是非常漂亮的,既考虑到它的优点,速度比较快是吧,那同时的话呢,也兼顾到说你survivor区这个对象的特点呢,就是朝生夕死的,诶咱们比较适合使用这样的方式,哎,下边就我放了一个图,咱们在这个survivval区或者叫from区或兔区里边啊,你看这里边一旦呢,过来以后呢,它这又交换了一下,诶谁空谁是图对吧?这呢就是咱们应用的这样的一个复制算法的一个场景啊,那么进而的话呢,我们也说明一个问题,说在这个老年代当中,我们能不能使用这个复制算法呀。老年代。那显然呢,是不是就不行了,因为老年代中我们说这个对象这个进行回收的话呢,它的这个回收的效率啊还是比较差的,就是你很多对象呢,它还是要持久的这个存活的,那你要用复制算法的话呢,它这个就是我们刚才说的很大部分的对象呢,都复制了一遍,这个效率呢,反而是变得更差了。OK,行,那么关于这个复制算法啊,咱们就介绍到这儿。
我来说两句