憨人笔记之JVM-垃圾收集器

228 阅读10分钟

话不多说,肝就完了。

垃圾收集器

在之前学习了垃圾回收算法,可以这么理解,垃圾收集算法相当于一套规则,它约束也什么样的对象可以被回收。而垃圾收集器可以看做为这套规范的执行者。对可回收的对象进行垃圾回收,释放内存。下面分别了解一下各类垃圾收集器的特点。首先看一张图

图中展示了总共7种分别作用于不同分代的垃圾收集器。其连线表示不同分代可以组合使用垃圾收集器,接下来对7种垃圾收集器进行详细的介绍。

Serial收集器

Serial收集器是最基本的收集器,作用于新生代,采用复制算法,它是采用单线程的收集器。需要注意的是,它所谓的单线程并仅仅是说使用一个CPU或一条收集线程去进行垃圾回收。最重要的是,它在进行垃圾回收工作的时,**会暂停其他所有正在工作的正常的线程。**直到垃圾收集结束,也被称作"Stop The World"。

通俗点理解就是,"我要开始打扫垃圾了,你们都停下来啥都不要动,等我干完了,你们再做自己的活"。看看图

途中将Serial收集器以及Serial收集器工作都标记了出来,结合图,就很容易理解"Stop The World",垃圾收集线程开始了,然后就在入口处亮起"红灯",此时所有的用户线程都暂停等待。当垃圾收集线程完成收集的时候,此时亮起"绿灯",所有用户线程开始正常工作。想想交通信号灯,好想有那么点意味。

由于是单线程的,所以也就没有了线程交互的开销,效率上相对来讲还是比较高的。目前它依然是虚拟机运行在Client模式下的默认新生代收集器。

ParNew收集器

ParNew收集器实际上就是Serial收集器的多线程版本。除了使用多线程进行垃圾回收之外,其余Serial收集器可用的控制参数、收集算法、Stop The World、对象分配规则、回收策略等它都可用。

ParNew收集器在虚拟机运行在Server模式下时,是默认的新生代收集器。与Serial收集器相比,由于它是多线程的,自然就会增加线程交互上的开销。所以性能上相对Serial收集器来说,差了那么一点。在多个CPU的情况下,可以通过**-XX:ParallelGCTherads参数来设置垃圾回收的线程数**。

Parallel Scavenge收集器

Parallel Scavenge同样的也是一个作用于新生代的垃圾收集器,采用复制算法。也是一个多线程的处理器。就这些特点比较起来,与ParNew收集器似乎没什么区别。细看一下,Parallel Scavenge收集器更关注的是达到一个可控制的吞吐量

吞吐量:CPU运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=用户代码运行时间/(用户代码运行时间+垃圾收集时间)

例如:CPU总消耗时间为100,而垃圾回收时间为1,用户运行代码时间为99,则吞吐量为99%

在之前说到,垃圾回收的时候会Stop The World,停顿的时间越短,则程序的响应速度也就越快。高的吞吐量可以有效的提高CPU的利用效率,让更多的时间用来运行用户代码,从而更快的完成程序的运算任务。

有两个参数用于设置Parallel Scavenge收集器的吞吐量。分别是控制最大停顿时间(-XX:MaxGCPauseMillis)、直接指定吞吐量大小(-XX:GCTimeRatio)

最大停顿时间(-XX:MaxGCPauseMillis)的单位为毫秒。值得注意的是GC停顿时间设置的越短,牺牲的是新生代的空间以及吞吐量。例如:新生代由500M调整为300M,300肯定比500收集要快,原来可能每10秒进行一次垃圾回收,每次回收停顿时间100毫秒,就可能变成没5秒回收一次,停顿时间变为70毫秒。所以看起来停顿时间是短了,但是空间小了,意味着收集频率就变高了。从10秒一次变成5秒一次,也就意味着每次收集都要停顿,用户代码运行的时间也相对少了。吞吐量也就降低了。所以全面的思考来看,过度的将停顿时间设置短,并不意味着能得到很好的效果。

由于与吞吐量密切相关,所以Parallel Scavenge收集器也被称为"吞吐量优先"收集器。除了上述两个参数之外,还有一个参数**-XX:+UseAdaptiveSizePolicy**。这是一个开关参数。将此开关参数设置之后,就不用手工设置新生代的大小以及Eden区与Survivor区的比例等细节参数了。虚拟机会根据实际运行情况自动的调整这些参数以达到最优的最大停顿时间或吞吐量。这种调节方式也被称为GC自适应策略。也是与ParNew收集器不同的地方。

Serial Old收集器

Serial Old收集器是Serial收集器的老年代版本。与Serial不同的是,它采用的是标记-整理算法。其主要也是在虚拟机的Client模式下使用。

在Server模式下,它有两大作用:

  1. 在JDK1.5以前,会搭配Parallel收集器使用。
  2. 作为CMS收集器的后备预案,在垃圾收集发生Concurrent Mode Failure时使用

Parallel Old 收集器

Parallel Old同样的也是作为Parallel Scavenge的老年代版本。采用的也是标记-整理算法。与Parallel Scavenge的区别在于作用的分代不一样。在JDK1.6以前,Parallel Scavenge只能够搭配Serial Old进行使用,并不能很好的发挥吞吐量优先的趋势。在Parallel Old出现之后,就能与Parallel Scavenge收集器作为新老年代的搭配。达到最优的"吞吐量优先"。

所以在注重吞吐量和CPU资源的场景下,可以优先选择Parallel组合。

CMS收集器

CMS收集器(Concurrent Mark Swap)是一种以获取最短回收时间为目标的垃圾收集器。垃圾回收时间短也就意味着停顿时间短,系统的响应速度也就相对来说较高。

与前面四种垃圾收集器相比,它相对会比较复杂。首先需要明确的是,它是采用的**"标记-清除算法"**。主要有以下四个步骤:

  • 初始标记:标记一下GC Roots能直接关联到的对象,Stop The World,但时间很短
  • 并发标记:GC Roots Tracing的过程,
  • 重新标记:修正并发标记阶段因用户程序继续运作而导致的标记产生变动的那一部分对象的标记记录,Stop The World,比初始比标记的时间要长,但是比并发标记的时间要短。
  • 并发清除:清除对象

这个过程与可达性算法的两次标记过程看起来有那么一点相像。并发标记和并发清除过程可以同用户线程一起执行,它们之间是一个并发的关系。如图:

CMS收集器的优点在于并发、停顿时间短,但是它也就明显的缺点:

  1. 对CPU的资源非常敏感,CMS默认的并发线程数为(CPU数量+3)/4,也就意味着当CPU数量为4个的时候,会有25%的CPU资源用来处理垃圾回收,相对来说分配给用户线程数量的CPU资源也就不那么充裕,会影响到系统执行的效率。

  2. 无法处理浮动垃圾,可能出现“Concurrent Mode Failure”而导致一次Full GC的产生。当出现“Concurrent Mode Failure”的时候,就会启动会被预案,使用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就会很长。

    Concurrent Mode Failure

    浮动垃圾

    CMS的并发清理线程是与用户线程并发执行的,随着程序的运行会不断有新的垃圾产生,这一部分垃圾可能出现在标记过程之后,这就意味着CMS无法在当次收集的过程中清除它们,只能够再下一次GC时清理,这一部分垃圾对象就被称为浮动垃圾

    由于在并发清理阶段,用户线程也是正常运行的,这就意味着会不断的有新对象的创建,那就需要足够的内从空间来给用户线程分配使用。CMS收集器就不能够向其他收集器一样等到老年代被填满了再进行收集,由于浮动垃圾的存在,可能导致这部分预留给用户线程的空间不够,就会出现"Concurrent Mode Failure",这个时候就会触发虚拟机的后备预案。

  3. 由于采用的"标记-清除算法",也就意味着垃圾回收会产生内存碎片。

G1收集器

G1收集器是目前虚拟机垃圾收集器中最前沿的技术之一。它是一款面向服务端的垃圾收集器。与其他垃圾收集器相比,它有以下特点:

  • 并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。
  • 分代收集:与其他收集器一样,分代的概念依旧存在。
  • 空间整合:与CMS收集器的"标记-清除"不同,从大的来看是采用"标记-整理算法",但是从小的来看(两个Regin之间)采用的是复制算法,总的来说,就是不会产生内存碎片。
  • 可预测停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

G1与其他收集器不一样,其他收集器回收的对象主要是新生代和老年代,而对于G1,堆的内存布局就有很大的差别,它依然有新生代和老年代的概念,但是不像其他收集器一样是物理隔离的,它将整个堆内存划分成不同的区(Region),新生代和老年代都是一部分Region的集合(Region不需要连续)

总结

G1收集器相对来说比较复杂,后续单独开一篇来记录G1收集器的学习过程吧,这一次就简单介绍以下。


不怕路歹行不怕大雨淋,心上一字敢 面对我的梦,甘愿来作憨人。 --<憨人>