垃圾收集(Garbage Collector,GC)

Java的内存分布

JVM中,内存是按照分代进行组织的。

垃圾收集(Garbage Collector,GC)_第1张图片

其中,堆内存分为年轻代年老代,非堆内存主要是Permanent区域,主要用于存储一些类的元数据,常量池等信息。而年轻代又分为两种,一种是Eden区域,另外一种是两个大小对等的Survivor区域。之所以将Java内存按照分代进行组织,主要是基于这样一个“弱假设” - 大多数对象都在年轻时候死亡。同时,将内存按照分代进行组织,使得我们可以在不同的分代上使用不同的垃圾回收算法,使得整个内存的垃圾回收更加有效。

年轻代总共有3块空间,其中2块为Survivor区。各个空间的执行顺序如下:

1.绝大多数新创建的对象分配在Eden区。

2.在Eden区发生一次GC后,存活的对象移到其中一个Survivor区。

3.在Eden区发生一次GC后,对象是存放到Survivor区,这个Survivor区已经存在其他存活的对象。

4.一旦一个Survivor区已满,存活的对象移动到另外一个Survivor区。然后之前那个空间已满Survivor区将置为空,没有任何数据。

5.经过重复多次这样的步骤后依旧存活的达到年龄阈值的对象将被移到老年代。(其实,即使对象的年龄不够大,但是Survivor内存区域中没有足够的空间来容纳从Eden升级过来的对象时,也会有部分对象直接升级到Tenured内存区域中)

垃圾回收的一般步骤:

从上面的介绍中我们已经理解了为什么堆被分成不同的代,下面我们就需要更精确的理解这些空间是如何进行交互的。下面的一组图片展示了JVM中垃圾回收的一般过程,从对象分配到对象逐渐变老。

1、首先,所有新生成的对象都是放在年轻代的Eden分区的,初始状态下两个Survivor分区都是空的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。

垃圾收集(Garbage Collector,GC)_第2张图片

2、当Eden区满的的时候,小垃圾收集就会被触发。

垃圾收集(Garbage Collector,GC)_第3张图片

3、当Eden分区进行清理的时候,会把引用对象移动到第一个Survivor分区,无引用的对象删除。

垃圾收集(Garbage Collector,GC)_第4张图片

4、在下一个小垃圾收集的时候,在Eden分区中会发生同样的事情:无引用的对象被删除,引用对象被移动到另外一个Survivor分区(S1)。此外,从上次小垃圾收集过程中第一个Survivor分区(S0)移动过来的对象年龄增加,然后被移动到S1。当所有的幸存对象移动到S1以后,S0Eden区都会被清理。注意到,此时的Survivor分区存储有不同年龄的对象。

垃圾收集(Garbage Collector,GC)_第5张图片

5、在下一个小垃圾收集,同样的过程反复进行。然而,此时Survivor分区的角色发生了互换,引用对象被移动到S0,幸存对象年龄增大。EdenS1被清理。

垃圾收集(Garbage Collector,GC)_第6张图片

6、这幅图展示了从年轻代到老年代的提升。当进行一个小垃圾收集之后,如果此时年老对象此时到达了某一个个年龄阈值(例子中使用的是8),JVM会把他们从年轻代提升到老年代。

垃圾收集(Garbage Collector,GC)_第7张图片

7、随着小垃圾收集的持续进行,对象将会被持续提升到老年代。

垃圾收集(Garbage Collector,GC)_第8张图片

8、这样几乎涵盖了年轻一代的整个过程。最终,在老年代将会进行大垃圾收集,这种收集方式会清理-压缩老年代空间

垃圾收集(Garbage Collector,GC)_第9张图片

年轻代的垃圾回收算法:

在年轻代上采用的垃圾回收算法是“Mark-Copy”算法,并不同于我们前面所了解的任何一种基本垃圾回收算法,但是Mark算法是一样的,基于根对象找到所有的可达对象,具体可看Mark-Sweep算法中的Mark步骤. 而对于Copy算法,它仅仅是简单的将符合一定年龄的对象从一个分代拷贝到另一个分代。

年老代的垃圾回收算法:

当minor gc发生时,又有对象从Survivor区域升级到Tenured区域(年老代),但是Tenured区域已经没有空间容纳新的对象了,那么这个时候就会触发年老代上的垃圾回收,我们称之为"major garbage collection".

而在年老代上选择的垃圾回收算法则取决于JVM上采用的是什么垃圾回收器。通过的垃圾回收器有两种:Parallel Scavenge(PS) Concurrent Mark Sweep(CMS)。这两种垃圾回收器的不同更多的是体现在年老代的垃圾回收过程中,年轻代的垃圾回收过程在这两种垃圾回收器中基本上是一致的。

就像其名字所表示的那样,Parallel Scavenge垃圾回收器在执行垃圾回收时使用了多线程来一起进行垃圾回收,这样可以提高垃圾回收的效率。而Concurrent Mark Sweep垃圾回收器在进行垃圾回收时,应用程序可以同时运行。

Parallel Scavenge

PS垃圾回收器在年老代上采用的垃圾回收算法可以看作是标记-清除算法标记-压缩算法的结合体。

首先,PS垃圾回收器先是会在年老代上使用标记-清除算法来回收掉非可达对象所占有的空间,但是我们知道,标记清除算法的一个缺陷就是它会引起内存碎片问题。继而有可能会引发连续的major gc。假设当前存在的内存碎片有10M,但最大的内存碎片只能容纳2M的对象,这个时候如果有一个3M的对象从Survivor区域升级到Tenured区域,那Tenured区域也没有办法存放这个3M的对象。结果就是不断的触发major gc,直到Out of Memory。所以,PS垃圾回收器在清除非可达对象后,还会进行一次compact,来消除内存碎片。

垃圾收集(Garbage Collector,GC)_第10张图片

Concurrent Mark Sweep

CMS垃圾收集器相比于PS垃圾收集器,它成功的减少了垃圾收集时暂停应用程序的时间,因为CMS在进行垃圾收集时,应用程序是可以并行运行的。下面让我们来看看它是怎么做到的。

从它的名字可以看出,CMS垃圾收集器在年老代上采用的垃圾回收算法是标记-清除算法。但是,它跟标准的标记-清除算法略有不同。它主要分为四个阶段:

1.Initial Mark阶段 - 这个阶段是Stop-The-World的,它会暂停应用程序的运行,但是在这里阶段,它不会标记出在Tenured区域中所有的可达对象。它只会从根对象开始出发,标记到根对象的第一层孩子节点即停止。然后恢复应用程序的运行。所以,这个暂停应用程序的时间是很短的。

2.Concurrent Mark阶段 - 在这个阶段中,CMS垃圾回收器以Initial Mark阶段标记的节点为根对象,重新开始标记Tenured区域中的可达对象。当然,在这个阶段中是不需要暂停应用程序的。这也是它称为"Concurrent Mark"的原因。这同时也造成了一个问题,那就是由于CMS垃圾回收器和应用程序同时运行,Concurrent Mark阶段它并不保证在Tenured区域的可达对象都被标记了 - 应用程序一直在分配新对象。

3.Remark阶段 - 由于Concurrent Mark阶段它并不保证在Tenured区域的可达对象都被标记了,所以我们需要再次暂停应用程序,确保所有的可达对象都被标记。为了加快速度,这里也采用了多线程来同时标记可达对象。

4.Concurrent Sweep阶段 - 最后,恢复应用程序的执行,同时CMS执行sweep,来清除所有非可达对象所占用的内存空间。

从下图可以看到PS和CMS垃圾收集器的区别:

垃圾收集(Garbage Collector,GC)_第11张图片

黑色箭头代表应用程序的运行,绿色箭头代表CMS垃圾收集器的运行。一根线条表示单线程,多个线条表示多线程。

所以,相比于PS垃圾收集器,CMS垃圾收集器成功的减少了应用程序暂时的时间。

Garbage First(G1)垃圾收集器

但是很不幸的是,CMS垃圾收集器虽然减少了暂停应用程序的运行时间,但是由于它没有Compact阶段,它还是存在着内存碎片问题。于是,为了去除内存碎片问题,同时又保留CMS垃圾收集器低暂停时间的优点,JAVA7发布了一个新的垃圾收集器 - G1垃圾收集器。它会在未来逐步替换掉CMS垃圾收集器。

G1垃圾收集器和CMS垃圾收集器有几点不同。首先,最大的不同是内存的组织方式变了。Eden,Survivor和Tenured等内存区域不再是连续的了,而是变成了一个个大小一样的region - 每个region从1M到32M不等。

垃圾收集(Garbage Collector,GC)_第12张图片

一个region有可能属于Eden,Survivor或者Tenured内存区域。图中的E表示该region属于Eden内存区域,S表示属于Survivor内存区域,T表示属于Tenured内存区域。图中空白的表示未使用的内存空间。G1垃圾收集器还增加了一种新的内存区域,叫做Humongous内存区域,如图中的H块。这种内存区域主要用于存储大对象-即大小超过一个region大小的50%的对象。

在G1垃圾收集器中,年轻代的垃圾回收过程跟PS垃圾收集器和CMS垃圾收集器差不多,新对象的分配还是在Eden region中,当所有Eden region的大小超过某个值时,触发minor gc,回收Eden region和Survivor region上的非可达对象,同时升级存活的可达对象到对应的Survivor region和Tenured region上。对象从Survivor region升级到Tenured region依然是取决于对象的年龄。

垃圾收集(Garbage Collector,GC)_第13张图片

对于年老代上的垃圾收集,G1垃圾收集器也分为4个阶段,基本跟CMS垃圾收集器一样,但略有不同:

Initial Mark阶段 - 同CMS垃圾收集器的Initial Mark阶段一样,G1也需要暂停应用程序的执行,它会标记从根对象出发,在根对象的第一层孩子节点中标记所有可达的对象。但是G1的垃圾收集器的Initial Mark阶段是跟minor gc一同发生的。也就是说,在G1中,你不用像在CMS那样,单独暂停应用程序的执行来运行Initial Mark阶段,而是在G1触发minor gc的时候一并将年老代上的Initial Mark给做了。

Concurrent Mark阶段 - 在这个阶段G1做的事情跟CMS一样。但G1同时还多做了一件事情,那就是,如果在Concurrent Mark阶段中,发现哪些Tenured region中对象的存活率很小或者基本没有对象存活,那么G1就会在这个阶段将其回收掉,而不用等到后面的clean up阶段。这也是Garbage First名字的由来。同时,在该阶段,G1会计算每个 region的对象存活率,方便后面的clean up阶段使用 。

Remark阶段 - 在这个阶段G1做的事情跟CMS一样, 但是采用的算法不同,G1采用一种叫做SATB(snapshot-at-the-begining)的算法能够在Remark阶段更快的标记可达对象。

Clean up/Copy阶段 - 在G1中,没有CMS中对应的Sweep阶段。相反 它有一个Clean up/Copy阶段,在这个阶段中,G1会挑选出那些对象存活率低的region进行回收,这个阶段也是和minor gc一同发生的,如下图所示:

垃圾收集(Garbage Collector,GC)_第14张图片

从上可以看到,由于Initial Mark阶段Clean up/Copy阶段都是跟minor gc(小垃圾回收==+)同时发生的,相比于CMS,G1暂停应用程序的时间更少,从而提高了垃圾回收的效率。

参考:

http://www.jianshu.com/p/778dd3848196

http://www.jianshu.com/p/57457a351b8a


你可能感兴趣的:(Java)