JVM-垃圾收集器


Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商,不同版本的虚拟机所提供的垃圾收集器可能会有很大差距. 

 HotSpot虚拟机示意图: 

 

  说明:两个收集器之间存在连线说明它们可以搭配使用.虚拟机所处的区域则表明它属于新生代收集器还是老年代收集器. 

Serial收集器
  这个收集器是一个单线程收集器,但是它是"单线程"的意义,并不仅仅说明它只会使用一个CPU或一条收集器去完成垃圾收集工作,更重要的是它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束.
  优点:简单而高效(与其他收集器的单线程相比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率.   

ParNew收集器
  ParNew收集器起始就是Serial收集器的多线程版本.
  除了Serial收集器外,目前只有ParNew能与CMS收集器配合工作.

Parallel Scavenge收集器
  Parallel Scavenge收集器是一个新生代收集器,也是使用复制算法的收集器,又是并行的多线程收集器.
  Parallel Scavenge收集器的目标是达到一个可控制的吞吐量(Throughout).
  吞吐量就是CPU用于运行用户代码的时间与CPU消耗时间的比值.
  吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)

 

  停顿时间越短就越适合需要与用户交互的程序,良好的相应速度能提升用户体验,而吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务.

 

  Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大对大垃圾收集停顿时间的-xx:MaxGCPauseMillis参数以及直接设置吞吐量大小的-xx:GCTimeRauo参数. 

Serial Old收集器
  Serial Old 是Serial收集器的老年代版本,是一个单线程的收集器.使用"标记-整理"算法.
  两大用途:
  a)在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用.
  b) 作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure 时使用.

Parallel Old收集器
  Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和"标记-整理"算法.
  在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器.

CMS收集器
  CMS(Concurrent Mark Sweep)收集器是一种一获取最短回收停顿时间为目标的收集器.
  CMS收集器是基于"标记-清除"算法实现的,整个过程分为4个步骤(仍然需要"Stop The Word"):
  I.初始化标记(CMS Concurrent Mark)
  II.并发标记(CMS Concurrent Mark)
  III.重新标记(CMS Remark)
  IV.并发请求(CMS Concurrent Sweeep)

 

  初始化标记仅仅是标记一下GC Root能直接关联到的对象,速度很快,并发标记阶段是进行GC Root Tracing的过程,而重新标记阶段则是为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但这远比并发标记的时间短.

  从总体上说,CMS收集器的内存回收过程是与用户线程一起并发执行的.

 

  缺点:
  I.CMS收集器对CPU资源非常敏感
  II.CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现"Concurrent Mode Failure"失败而导致另一次Full GC的产生.
  III.CMS是基于"标记-清除"算法实现的收集器,意味着收集结束时会有大量的空间碎片产生.

G1收集器
  G1(Garbage-First)收集器是当今收集器是当今收集器技术发展的最前沿成果之一.
  G1是一款面向服务端应用的垃圾收集器,具备一下节点:
  I.并行与并发:G1能充分利用多CPU,多核环境下的硬件优势,使用多个CPU来缩短Stop_The_Word停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行.
  II.分代收集
  III.空间整合:G1从整体来看是基于"标记-整理"算法实现的收集器.从局部(两个Regin之间)上看是基于"复制"算法实现的,但是无论如何,这两种算法都意味着G1运行期间不会产生内存空间碎片,收集后能提供规整的可用内存.这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续空间而提前触发下一次GC.
  IV.可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但是G1除了追求低停顿外,还能建立可预测的时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java的垃圾收集器的特征了.

 

  使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,他将整个Java堆划分为多个大小相等的独立区域(Regin),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Regin(不需要连续)的集合.

 

  G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集.G1跟踪各个Regin里面的垃圾堆积的价值大小,在后台维护一个有限列表,每次根据允许的收集时间,优化回收价值最大的Regin(这就是Garbage-First名称的由来).

 

  在G1收集器中.Regin之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用,虚拟机都是使用Remenbered Set 来避免全堆扫描的.G1中每个Regin都有一个与之对应的Remenbered Set,虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Regin之中(在分代的例子中就是检查是否老年代中的对象引用了新生代的对象).如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Regin的Remenbered Set即可保证不对全堆扫描也不会有遗漏.

 

  G1收集器的运作大致可划分为一下几个步骤:
  I.初始化标记(Initial Marking)
  II.并发标记(Concurrent Marking)
  III.最终标记(Final Marking)
  IV. 筛选回收(Live Data Counting and Evacuation)

 

  初始标记阶段仅仅是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Regin中创建对象.这阶段需要停顿线程,但耗时很短.
  并发标记阶段是从GC Root 开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户进程并发执行.
  最终标记阶段则是为了修正在并发标记期间因用户程序继续执行运作导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remenbered Set Logs里面,最终标记阶段需要把Remenbered Set Logs的数据合并到Remenbered Set中,这个阶段需要停顿线程,但是可并行执行.
  筛选回收阶段首先对各个Regin的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划.这个阶段也可以做到与用户程序一起并发执行,但是因为回收一部分Regin,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率. 

本文永久更新链接地址

相关内容