深入理解Java虚拟机第三章读书笔记:HotSpot虚拟机中的各个垃圾收集器

1.前言

垃圾收集算法是内存回收的方法论,那么垃圾收集器就是垃圾回收的具体实现。书中提到的HotSpot虚拟机所实现的所有垃圾收集器如图所示,有连线的两个垃圾收集器表示这两种垃圾收集器可以搭配使用,垃圾收集器所处的区域表明它适用的区域,图中横线上方是新生代区域,下方是老年代区域

在垃圾收集器的上下文语境中,并发和并行的解释如下:

  • 并行:多条垃圾收集线程同时工作,但是用户线程还处在停滞等待状态中
  • 并发:用户线程与垃圾收集线程同时执行(不一定是并行的,也有可能是交替执行),用户程序在继续执行,而垃圾收集线程运行于另外一个CPU上

2.各种垃圾收集器

2.1 Serial收集器(用于新生代,使用复制算法)

serial收集器是历史最悠久,最基本的垃圾收集器。

它是一个单线程的收集器,这个单线程并不仅仅只是说明它只用一个CPU或者一个收集线程去进行垃圾收集,更中重要的是它在进行垃圾收集工作的时候要暂停其他所有的用户进程,直至它工作结束,也就是常说的“Stop The World”。

缺点:

  • 收集线程工作时,要暂停其他的所有进程。就好比说电脑用了一个小时,然后要暂停个五分钟让serial收集器去工作,这样的用户体验是很不好的。

优点:

  • 简单高效(与其他单线程的收集器相比)。在单个CPU的环境来说,serial收集器由于没有线程交互的额外开销,专心的做垃圾收集工作自然可以获得最高的单线程收集效益。
  • Serial收集器对于运行在Client模式下的虚拟机来说任然是一个很好的选择。并且它是虚拟机运行在Client模式下的默认新生代收集器。

2.2 ParNew收集器(用于新生代,使用复制算法)

ParNew收集器就是Serial收集器的多线程版本。

除了是使用多线程进行垃圾收集以外,其余的包括控制参数、收集算法、STW、对象分配规则、回收策略等都与Serial收集器一模一样。

ParNew收集器是运行在Server模式下的虚拟机中首选的新生代收集器 ,有一个与性能无关但是很重要的原因是只有它才能跟CMS收集器进行配合工作。

ParNew收集器在单CPU的情况下不会有比Serial收集器更好的效果,甚至因为存在多线程交互而产生的开销,在两个CPU的环境中都不能百分之百的保证性能可以超过Serial收集器。

2.3 Parallel Scavenge收集器(用于新生代,使用复制算法)

Parallel Scavenge收集器又被称为“吞吐优先”收集器。

Parallel Scavenge收集器的特点是它与其他收集器的关注点不同,它关注的是吞吐量,CMS等收集器的关注点是尽量缩短垃圾收集时用户线程的停顿等待时间

吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。例如虚拟机共执行了100分钟,垃圾收集花掉了1分钟,那么吞吐量就是99%。

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

2.4 Serial Old收集器(用于老年代,使用标记-整理算法)

Serial Old收集器是Serial收集器的老年版本,也是有一个单线程的垃圾收集器。

他存在的主要作用也是给运行在Client模式下的虚拟机使用。

在Server模式下使用,它还有两大用途:

  • 一种是在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用
  • 另一种就是作为CMS收集器的后被预案,在并发收集中出现Concurrent Mode Failure时使用

2.5 Parallel Old收集器(用于老年代,使用标记-整理算法)

Parallel Old收集器是Parallel Scavenge收集器的老年代版本。

在注重吞吐量以及CPU资源敏感的场景下,都可以优先考虑使用Parallel Scavenge + Parallel Old收集器

2.6 CMS收集器(用于老年代,使用标记-清除算法)

CMS收集是一种以回收停顿时间最短为目标的收集器。

目前很大一部分的Java应用都是集中在互联网站和B/S系统的服务器上,这类应用尤其重视服务的响应速度,希望系统的停顿时间最短,以便给用户带来最好的用户体验。CMS收集器就非常符合这类应用的需求。

CMS收集器的运作过程分为4个步骤:

  1. 初始标记-------->需要Stop The World,初始标记仅仅是标记一下GC Roots能直接关联到的对象,速度很快
  2. 并发标记-------->进行GC Roots Tracing的过程,这个阶段GC线程与用户线程并发执行,由前阶段已经标记过的对象出发,标记那些与Gc Roots间接关联的对象,所有GC Roots可达的对象都会在本阶段被标记完成
  3. 重新标记-------->在并发标记阶段可能有些对象从新生代升到了老年代,或者有些对象被进行了修改,那么通过这个阶段来重新标记这些对象,此阶段需要Stop The World,并且这个阶段是多线程的
  4. 并发清除-------->用户进程被重新激活,GC线程将无用的对象进行清除

CMS收集器的3个缺点:

  • CMS收集器对CPU资源非常敏感。在并发阶段他虽然不会导致用户线程停顿,但是因为它占用了一部分的CPU资源所以会导致应用程序变慢。CMS默认开启的线程数是(CPU数量+3)/ 4,也就是当CPU数量在4个以上时,并发回收时垃圾收集线程会占用不少于25%的CPU资源,并且随着CPU的数量增加而下降。但是当CPU不足4个,比如两个的话,就需要分出一半的资源去执行收集器线程。
  • CMS没有办法清理“浮动垃圾”(因为CMS在并发清除阶段时,用户线程还在进行,伴随用户线程运行的同时,还会产生新的垃圾,而CMS无法在当次处理时对他们进行清理,这些垃圾就叫做浮动垃圾),可能出现Concurrent Mode Failure失败而导致另一次Full GC的执行。CMS无法在当次的垃圾清理中对浮动垃圾进行清除,所以只能留待下一次GC时再进行清理。因为垃圾清理的同时用户线程也在执行,所以还需要留出足够的内存空间来给用户线程使用,所以CMS收集器没有办法像其他收集器一样等到老年代空间满了才进行清理,必须要预留一部分空间提供给并发执行的用户线程使用。在JDK1.6中,只要老年代使用了92%,那么CMS就会启动。要是在CMS运行期间预留的内存无法满足用户线程的需求,那么就会出现一次Concurrent Mode Failure失败,这时虚拟机就会启动备案:即启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿的时间就很长了。
  • CMS使用的是“标记-清除”算法,所以会产生空间碎片,可能导致给大对象分配内存空间时,连续内存不足,从而导致提前触发一次Full GC,为了解决这个问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数(默认打开),用于在CMS收集器顶不住要进行Full GC的时候开启内存碎片的合并整理过程,内存整理的过程是无法并发的,所以要停掉用户线程,从而使得用户线程停顿时间加长,背离了CMS收集器的初衷。

2.7 G1收集器(跨代收集器,即可用于新生代也可以用于老年代)

G1(Garbage First)收集器是当今收集器技术发展最前沿的成果之一。

G1收集器是一款面向服务端应用的垃圾收集器。HotSpot团队的目标是让G1收集器日后能够代替CMS收集器。

与其他收集器相比,G1收集器具备以下的特点:

  1. 并发与并行:G1收集器可以有效的利用多CPU资源,多核环境下的硬件优势。当其他收集器执行GC线程的时候,会Stop The World,但是G1收集器任然可以使用并发来使用户线程继续执行
  2. 分代收集:虽然G1收集器可以不需要其他的收集器配合,就可以独立管理整个GC堆,但是在不同的区域,它任然使用了不同的收集策略以达到更好的GC效果
  3. 内存整理:不同于CMS的标记-清除算法,G1使用了标记-整理算法以及复制收集算法,从而避免了空间碎片的问题。收集后能够保证堆内存的规整。这种特性有利于程序的长时间运行,分配大对象时不会因为无法得到足够的内存空间而提前触发下一次GC
  4. 可预测的停顿,这是G1相对于CMS的另一大优势。降低停顿时间是G1和CMS共同的关注点。G1除了追求低停顿以外还可以建立可预测的停顿时间模型,能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不超过N毫秒

前面介绍的几款收集器都是只能用于新生代或者只能用于老年代,而G1既能够收集新生代又可以收集老年代是因为它把堆内存划分成多个大小相等的独立区域Region,新生代和老年代不再是物理隔离了,他们是多个Region的集合(Region可以物理上不连续)。

G1之所以可以建立可预测的停顿模型,是因为它可以有计划的避免对整个Java堆进行全区域垃圾收集,G1跟踪各个Reigon里面的垃圾堆积的价值大小(回收所获的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,有限回收价值最大的Region。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效益。

G1收集器中,Region之间的对象引用以及其他收集器的新生代和老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描的。G1中的每一个Region都会有一个Remembered Set来与之对应。虚拟机发现程序在对reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查reference引用的对象是都处于同一个Region,如果不是,那么就通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set中。当进行内存回收时,在GC根节点的枚举范围内加入Remembered Set即可保证不对全堆扫描也不会有遗漏。

G1收集器的运作大致可以分为以下四个阶段:

  1. 初始标记:标记与GC Roots直接关联的对象(需要线程停顿,但是耗时很短)
  2. 并发标记:从GC Roots开始对堆中对象进行可达性分析,找出所有与GC Roots相关联的存活对象(可以与用户线程并发执行,耗时较长)
  3. 最终标记:为了修正在并发标记期间因为用户线程运行而导致标记产生改变的对象(需要停顿用户线程,但是可以并行执行)
  4. 筛选回收:首先对各个Region的回收价值进行排序,根据用户所期望的GC停顿时间来制定回收计划,这个阶段其实是可以与用户线程并发执行的,但是用户线程停顿的话会得到更高的收集效率

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值