JVM垃圾收集算法和垃圾收集器原理

一、垃圾收集算法

1、标记清除

? ? ? ? 标记清除主要分两个步骤,先标记出需要回收的对象(对象存活判定在《Java中对象的创建、内存分配和销毁》一文中有说明),标记完成后一并回收所有对象。这种算法有个不好的地方就是会产生内存碎片。内存碎片过多会导致无法分配大块连续空间而再次引发GC。而且标记和清除两个阶段的效率并不是很高。

2、复制算法

? ? ? ? 为了解决以上算法的空间和效率问题,复制算法出现了。它出现的原本意图是将可用空间划分为大小相等的两块区域,每次只使用其中的一块,每次GC时都将存活的对象复制到另一块区域,把使用过的区域全部清理掉,这样就解决了内存碎片的问题。但是这种算法是以牺牲空间为代价的。

? ? ? ? 很多资料以及官方表明,Java中很多对象都是生命周期比较短的,实践证明也的确如此,因此经过一次回收后,能够存活下来的对象并不多,所以复制算法一般用在新生代的收集上。新生代分为Eden、S1、S2三块区域,它们默认的比例是:8:1:1,新生对象首先会在Eden去分配内存,新生代的垃圾回收后剩余的对象会被复制到其中一个幸存区,如果幸存区不够容纳剩余的对象,那么就会被分配到老年代。

3、标记整理

? ? ? ? 复制算法在对象存活率高的情况下将会变得非常的低效,所以老年代的回收一般不会选用复制算法。这时候标记整理算法出现了,标记过程和标记清除算法是一样的,不同的是,存活的对象都向内存区域的一端移动,然后清理掉边界意外的内存区域。

二、垃圾收集器

1、Serial

? ? ? ? 它是一个采用复制算法的串行的单线程收集器。用在新生代,它在工作时只会占用一个CPU一条线程,并且会停止掉所有用户线程,也就是业界说的SWT。它比较适用于小型应用,例如Client模式的桌面应用程序,一般内存占用顶多才几百兆,回收起来也很快,几十毫秒几百毫秒的停顿还可以忍受。使用-XX:+UseSerialGC来显式指定。

2、ParNew

? ? ? ? 它也是采用复制算法的收集器,用于新生代。其原理和Serial收集器一样,唯一不同的地方时,它是多线程垃圾收集器。使用-XX:+UseParNewGC参数来显式指定

3、Parallel Scavenge

? ? ? ? 它是也是一个新生代使用复制算法的收集器,也是多线程并行的收集器。也称为吞吐量收集器。在Java8中是默认的新生代收集器,它是一个吞吐量优先的收集器,吞吐量=运行用户代码时间/(运行用户代码时间 + 垃圾收集时间)。该收集器提供两个参数来作为优化目标,一个是允许垃圾收集的最大停顿时间:-XXMaxGCPauseMillis,默认值为很大的一个数18446744073709551615,它的单位是毫秒,这能忍?另一个是吞吐量:-XX:GCTimeRatio,默认值为99,取值范围是1 - 100。还有一个与ParNew有着重大区别的特点是自动内存调节,给定虚拟机一个目标,也就是设置吞吐量和允许的最大停顿时间后,虚拟机会在运行时统计收集性能数据,来动态调节Eden、Survivor空间的比例来达到用户设置的目标,参数是:-XX:+UseAdaptiveSizePolicy,Java8默认情况下这个参数是启用的。

4、Serial Old

? ? ? ? 它是Serial收集器的老年代版本,使用标记整理算法。它的存在有两个用途,一个是作为CMS收集器的备点,当CMS收集器出现Concurrent Mode Failure的时候会临时启用Serial Old收集器进行垃圾回收,另外一个是在Java5以及之前版本和Parallel Scavenge收集器配合使用。

5、Parallel Old

? ? ? ? 它是Parallel Scavenge的老年代版本,使用标记整理算法。从Java6开始提供,在Java6以前,Parallel Scavenge新生代收集器只能和Serial Old收集器配合使用,到了Java6及以后Parallel Scavenge和Parallel Old是默认搭配。

6、CMS

? ? ? ? CMS收集器是一个以最少停顿时间为目标的老年代收集器,全名叫做:Concurrent Mark Sweep,采用标记清除算法。它是一款真正意义上的并发收集器。CMS收集一共有四个步骤:初始标记、并发标记、重新标记、并发清除。其中初始标记和重新标记两个步骤仍然需要STW。初始标记只是简单的标记一下根节点能够直接关联到的对象,速度是很快的,而并发标记是从根节点枚举追中的过程,这个时间相比初始标记长很多,由于是与用户线程并发执行,所以不会导致用户线程停顿。重新标记是为了标记在并发标记时由于用户线程的运行而导致引用链变动的对象。并发清除就是对垃圾的清理操作。

? ? ? ? 内存中各个区域设置合理的情况下,它的优点还是很明显的,并发收集,低停顿。它的不足之处是,由于是并发收集,所以在GC的时候会抢占用户线程的CPU资源,其实以此换来应用的低停顿,还是值得的。由于CMS使用的是标记清除的算法回收内存空间,所以避免不了有内存碎片的产生,当需要分配大块连续空间的时候,不得不再次触发GC,可能导致Concurrent Mode Failure失败而导致再一次的FGC,当出现失败时,虚拟机会临时采用Serial Old收集器重新收集。可以使用-XX:CMSInitiatingOccupancyFraction=65参数来设置触发老年代GC的百分比,避免在内存很紧张的时候才进行GC。所以要想用好CMS收集器,就必须合理的设置新生代中各个区间的比例,尽量避免新生对象晋升到老年代。可以使用-XX:MaxTenuringThreshold=15来设置晋升到老年代的年龄,这是一个参考值,默认是15。

更多好问,请关注微信公众号

推荐阅读更多精彩内容