垃圾收集器总结


垃圾收集器总结

Serial 和 Serial Old

Serial是历史最悠久的收集器,包括用于新生代的 Serial 收集器和用于老年代的 Serial Old 收集器,它的主要特点为单线程工作,如下图所示:

Serial

优点:单线程工作,可以获得最高的单线程回收效率

缺点:GC时会停顿用户线程

ParNew 收集器

ParNew收集器是Serial收集器的多线程版本,除此之外和Serial几乎没有差别。在多个cpu核心的情况下比Serial性能更好。

但是ParNew收集器目前唯一的用途只有和CMS搭配使用,CMS只能和Serial和ParNew搭配,这就导致多cpu核心的情况下,想使用CMS,新生代就只有ParNew一个选择。

它的工作流程如下图所示:

ParNew

Parallel Scanvenge 收集器

这是JDK1.8的默认收集器。

它的诸多特性和ParNew非常相似,同样是新生代收集器、同样基于标记-复制算法、同样支持并行收集的多线程收集器……

它的特别之处在于,它的关注点在于获得尽可能高的吞吐量。

Parallel Scanvenge收集器的工作流程如下图:

parallel-scavenge

Parallel Scanvenge收集器提供了两个参数来精准控制吞吐量:

  1. 控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis, 它的参数允许一个大于0的毫秒数,收集器尽力保证GC时间不超过用户设置的时间
  2. 直接设置吞吐量大小的-XX:GCTimeRatio,值为一个正整数N,表示用户期望虚拟机的GC时间不超过程序运行时间的1/(1+N)。默认值为99

除此之外,Parallel Scanvenge还有一个参数:

-XX:UseAdaptiveSizePolicy,这是一个开关参数,激活后,会启动自动调节策略,使得新生代的大小-XX:Xmn、Eden与Survivor的比例-XX:SurvivorRatio、晋升老年代对象大小-XX:PretenureSizeThreshold等参数不需要我们手动调节了。

Parallel Old 收集器

Parallel Old收集器是Parallel Scanvenge的老年代版本,同样支持多线程并发收集,基于标记-整理算法实现。

它是Parallel Scanvenge的搭档,一般二者搭配使用.

CMS 收集器

和Parallel Scanvenge不同,CMS的出发点在于获得尽可能低的延迟时间。这种垃圾收集器适合用在互联网网站、B/S系统上,因为这些更关注用户的体验,希望系统的停顿时间尽可能的短。CMS采用增量更新算法实现。

CMS 收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:

  • 初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
  • 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
  • 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
  • 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。

如图所示:

CMS

CMS的缺点如下:

  • CMS对处理器资源敏感。CMS回收线程数默认是(处理器核心数量+3)/4 ,如果处理器核心在4个以上,回收线程只会占用不到25%的处理器资源,但当处理器核心不足4个时,CMS会对用户线程产生比较大的影响。
  • 无法处理浮动垃圾。浮动垃圾是指在CMS的并发标记和并发清理阶段,用户线程仍在运行,也就会产生新的垃圾,这些垃圾只能等到下一次GC一起回收。
  • CMS基于标记-清除算法实现,会产生大量的内存碎片,当没有足够空间来分配大对象就会触发Full GC。

G1 收集器

G1收集器基于原始快照方法实现,也是面向低延迟的垃圾收集器。它是一个飞跃,主要体现在:

  1. G1是面向全堆的收集器,也就是可以同时处理新生代和老年代,而不需要搭配两种垃圾收集器
  2. G1可以预测它GC的停顿时间

G1的工作原理:

G1把连续的堆内存划分为多个大小相等且独立的Region, 每一个Region可以根据需要扮演新生代、老年代等不同的角色。Region中有一个Humongous区域专门用来存储大对象,G1认为只要对象超过了Region大小的一半就认为是大对象,如果一个对象很大可能会用N个连续的Humongous Region存放。

G1之所以可以预测停顿时间,是因为它将Region作为最小回收单元,每次收集到的空间都是Region的整数倍。G1会跟踪Region中垃圾堆积的价值大小,价值即为回收所获得的空间以及回收时间的经验值,在后台维护优先级列表,优先收集价值更大的Region。

G1 收集器的运作大致分为以下几个步骤:

  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收

​ G1收集器除了并发标记,其他阶段也要完全暂停用户线程的执行。换言之,它并非纯粹追求低延迟,而是在延迟可控的前提下尽可能提升吞吐量。

G1的缺点:

  1. G1要为每个Region维护一个记忆集,本质上这个记忆集是一个哈希表,用于存放Region的起始地址和卡表的索引号。由于Region数量很多,所以记忆集会占用大量的堆内存,通常在java堆容量的10%-20%。
  2. G1同样无法处理浮动垃圾,当垃圾收集速度慢于对象分配速度,它也会被迫冻结用户线程运行,产生时间较长的Full GC
  3. G1的停顿时间预测模型基于衰减均值实现。G1会收集每个Region的回收耗时、每个Region记忆集里的脏卡数量等各个可预测的步骤花费的成本。

G1和CMS的对比:

G1的优势:

  1. 可以指定最大停顿时间
  2. 分Region的内存布局
  3. 按收益动态确定回收集
  4. 不会产生内存空间碎片

G1的劣势:

  1. 内存占用角度上,G1和CMS都是用卡表来处理跨代指针,但G1更复杂,内存占用很高,且维护成本也高,尤其新生代的对象引用变化频繁,维护卡表的开销也是比较大的
  2. 执行负载的角度上,CMS使用写后屏障维护卡表,G1除了写后屏障(G1的卡表更复杂,其实这一步就已经比CMS更繁琐),为了实现原始快照算法,还需要使用写前屏障来跟踪并发时的指针变化情况。所以G1会比CMS消耗更多运算资源。

文章作者: 山川大海
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 山川大海 !
  目录