(36讲)垃圾回收算法——复制算法

我们上一节已经了解过垃圾回收算法的第一种,叫做标记-清除算法,这种算法有两大问题,第一大问题就是它的效率问题,我们本节课来讲另外一个垃圾回收算法,就是叫做复制算法,这个算法就是来解决标记-清除算法的效率问题的,在讲复制算法之前,我们再来回顾一下,Java虚拟机的内存结构Java虚拟机的内存从大的方面来讲,主要分两大块,一块是被线程所共享的,另外一块是线程所独享的区域。被线程所共享的区域,主要有两大块,一块是堆内存,另一块是方法区,被线程所独享的区域主要有三大块,一块是栈内存,第二块叫本地方法栈,第三块叫程序计数器。这是我们之前所了解的,这是没有任何问题的,那么,我们说堆内存是垃圾回收所要针对的一块目标区域,那么,为了方便我们的垃圾回收,也就是说,因为这些算法的存在,为了方便垃圾回收,堆内存现在又可以被细分了,细分成哪些区域呢?还是先从大的方面来讲,那么,它会分为两块区域,也就是我们经常听到的,叫做新生代和老年代,那么,具体的为什么要这么分呢?我们后面在讲这个算法的时候,会说到,老年代是垃圾回收并不是特别关注的,主要关注的是新生代,新生代又会被进行再划分划分成一般是三个或者四个区域,第一个是Eden,英文翻译过来是伊甸园,它是干嘛的呢?我们只要是在创建对象的过程中,创建一个,那个这个对象就会扔到Eden这块区域中,只要是新创建的,那么就会扔到Eden中,让他们在那里过无忧无虑的生活,当然了,垃圾回收器也是最喜欢光顾Eden的,那么,一旦被垃圾回收所定位了,那么它就不再在Eden中了,那么,可能就被杀掉了,如果没被杀掉,就被放到Survivor即存活区中,那么就相当于,我有诺亚方舟的船票,那么我就进到了存活区里面,如果没有,就被杀掉了,还有一种就是,我多次在存活区里面,那么可能,我一直可能会被,地位比较高,在这里面上升了,升到哪里去了呢?老了,最后也没退休,就进入到Tenured Gen这一块区域中,去养老去了,基本上,我们的垃圾回收比较少关注Tenured Gen这一块区域,这是新生代三块内存区域的划分。

了解完内存划分之后,我们再来看复制算法,复制算法提高了标记-清除算法的性能,那它是如何做到的呢?我们来画个图

这个是堆内存,在这里,复制算法把它们分成了两块,

用的时候只用一块,在这里面用,在这里面去创建

创建完之后,进行一次垃圾回收,比如说,把我们标记的这些都给回收掉了,

回收掉了之后怎么办呢?它不是像我们原来的那块区域一样把它直接干掉了剩下一对不连续的空间,它是把没有回收掉的扔到另外一块区域里面去,并且给它们排列好,那么这块内存就是连续的了,

然后把上面所没有用到的,就是说,复制到下面完了,而且把这些都给清除掉了

这块内存区域就给清空了,然后接着再来分配空间,就在新的这块区域里面进行分配就行了

那么,下一次垃圾回收,同样的道理,把这里面被标记的

删掉,把没有被标记的,复制到上面去,那么,下面就是下一次的执行

这就是所谓的复制算法,那么,这个算法呢,我们可以看到,效率问题可以解决了但是又引入了一个新的问题,就是,内存区域同时用的只有一半,这样就会造成内存区域极大的浪费,内存资源也是非常宝贵的,为了解决这种内存区域的极大浪费问题,我们采用的方式就是,把堆内存进行划分,我们主要看这三块区域

我们认为整个的堆内存分成了三块区域,

这三块分别是什么呢?就是我们上面所说的,最大的这块区域就是伊甸园,所有创建的新对象都会往伊甸园中扔,所以,伊甸园这块内存区域是非常大的。第二块叫Survivor,第三块区域同样也称之为Survivor,

这是三块内存区域,这三块内存区域它是怎么去使用的呢?我们在分出来一块的

我们认为暂且是四块区域,我们看到实际上Survivor有两块区域,其实在Survivor,Eden,Tenured Gen这三块区域中,Eden这块区域一般是占到百分之八十以上,Survivor这两块区域分别占百分之十,我们暂且不管Tenured Gen,我们暂且不用Tenured Gen,

后面,我们如果不够的时候,我们会使用内存担保,会去找Tenured Gen要内存,但是,一般情况下,我们暂时先不用Tenured Gen。

为什么会有两块Survivor区域呢?过程是这样的,首先是创建对象的时候,就扔到Eden中了,Eden中就会扔很多的对象,

如果这里面一旦满了的话,就会去使用任意一个Survivor

这个Survivor其实就类似于这两块区域

这两块区域都称为Svrvivor。然后,在进行垃圾回收的过程当中

这一块区域是回收的主要区域,我们认为它回收的概率基本上就把这块区域中的几乎是全部都能回收,然后把存活的对象,我们认为,存活的对象一般是占到百分之十左右,那么,我们就把存活的对象移到这一块区域中来,

那么,这一块区域中就有对象了,然后这一部分全部清除掉

然后,这一部分同样也清除掉

然后,下一次再进行对象创建的过程中,我们就把对象接着往Eden中分

存活的对象还在这里面

如果说

这里面分的内存达到一定限度了,或者说内存报警了,等等一些情况下,那么,垃圾回收又开始工作了,那么就会把这里面存活的对象接着往这里面去放

并且会检测这里面的对象是否依然存活,那么,如果多数存活,那么,可能就进入到这一块区域了

当然这个不一定,我们后面再去说。然后,接着把这里的存活对象

和这里的存活对象

挪到这里面来

这样就完成了垃圾回收,我们发现,这样来进行使用的过程中,我们的内存并不会浪费太多,仅仅是浪费了百分之十,这个是可以接受的,这样我们看到,既提高了效率,而且空间浪费也并不是特别大。然后,下面接着说,发现,我们并不一定说

这里面百分之十是存活的,如果,它有百分之二十存活,那么,挪到Survivor中,Survivor中是装不下的,装不下怎么办?你总不能直接报内存溢出,这是不可行的,这个时候就需要一个内存的担保,就是所谓的分配担保,其实这个就跟银行贷款是一样的,如果信誉好,有很高的情况下都能按时偿还,那么,银行在进行下一次的贷款的时候,可能找一个担保人就可以了,如果不能按时还款,银行就从担保人那里扣钱就可以了,内存分配担保也是一样的,如果这一块区域

已经不够了,放不下了,那么就扔到老年代里面去,关于内存分配担保的问题,后面会讲。这里只要知道就行了,就是说当内存区域不够了,我们依然是有解决方案的,所谓的解决方案就是内存担保。

关于复制算法,就说到这里。我们主要了解算法的思想以及关于内存浪费的一个解决方案。

猜你喜欢

转载自blog.csdn.net/G_66_hero/article/details/86513678
今日推荐