JVM垃圾回收器笔记

在这里插入图片描述

垃圾收集器的组合方式

在这里插入图片描述

Serial收集器

工作原理

Serial收集器是最基础、历史最悠久的收集器。这个收集器是单线程工作的收集器,其单线程的意义不仅仅是说其只会使用一个处理器或者一条线程去完成垃圾收集工作,更重要的是指其在进行垃圾收集的过程中必须暂停其他所有的工作线程,直到它收集完成。
其收集过程如下图所示.
在这里插入图片描述
我们可以看到Serial垃圾收集器在进行垃圾回收的时候必须暂停用户线程,即STW,会导致用户出现短期的卡顿现象。且其在新生代采取标记-复制算法进行回收,在老年代采用标记-整理算法进行垃圾收集

工作区域

Serial作用于新生代
Serial Old 作用域老年代

搭配使用

  • Serial 回收新生代 + Serial Old 回收老年代

应用场景

  • 其是客户端模式下的默认新生代垃圾收集器。
  • 对于单核处理器或处理器核心数较少时,Serial是不错的选择

优缺点

优点

  • 实现简单且高效
  • 适用于单核处理器或处理器核心较少时
  • 额外内存消耗最小(相比于CMS、G1等需要内存担保策略的垃圾收集器

    缺点

  • 在多核心机器上不能发挥多核的优势
  • 如果STW时间过长可能影响用户体验

ParNew收集器

工作原理

ParNew收集器实质上是Serial收集器的多线程并行版本。作用于新生代,采用标记-复制算法进行垃圾回收,在回收的时候会产生STW

ParNew收集器除了支持多线程并行收集之外、其他与Serial收集器相比并没有太多创新之处,但他却是不少运行在服务端模式下的HotSpot虚拟机。尤其是JDK7之前的遗留系统中首选的新生代收集器,其中有一个与功能、性能无关但其实很重要的原因是: 除了Serial收集器外、目前只有它能与CMS收集器配合工作
在这里插入图片描述

工作区域

作用域新生代,并发收集,需要STW。

搭配使用

  • ParNew 收集新生代 + CMS收集老年代(备用Serial Old)

应用场景

  • 在单核心处理器的环境中ParNew收集器的效果绝对不会比Serial效果好
  • 在多核心处理器的环境中,ParNew收集器进行垃圾收集时,可以对系统资源高效利用还是很有好处的。
  • 默认开启的收集线程数与处理器核心数量相同。

优缺点

优点

  • 相较于Serial对于新生代的收集 启用了多线程收集,在多核心处理器环境下,更能对系统资源充分利用,降低STW时间
  • 配合CMS收集器搭配使用

缺点

  • 在单核心处理器或处理器核心较少时,效率不如Serial高效

Parallel Scavenge收集器

工作原理

Parallel Scavenge收集器是一款新生代收集器,基于标记-复制算法实现的垃圾收集器。也是能够并行收集的多线程收集器。Parallel Scavenge收集器看似与ParNew非常相似,但其与ParNew的关注点并不同。

Parallel Scavenge收集器的目标是达到一个可控制的吞吐量。所谓吞吐量就是指运行用户代码与处理器总消耗时间的比值。即 吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 运行垃圾收集时间)
举个例子:虚拟机完成某个任务,用户代码加上垃圾回收总共花费了100分钟,其中垃圾回收花费了1分钟,那么吞吐量就是99%。
停顿时间越短就越适合需要与用户交互或者保证服务响应质量的程序,良好的响应时间能提升用户体验

Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量

  • 控制最大垃圾收集停顿时间 -XX:MaxGCPauseMillis
  • 设置吞吐量大小: -XX:GCTimeRatio

由于与吞吐量关系密切,Parallel Scavenge收集器也经常被称作“吞吐量优先收集器”。除了上述两个参数之外,Parallel Scavenge收集器还有一个参数: -XX:+UseAdaptiveSizePolicy值得我们关注。这是一个开关参数,当这个参数被激活后,就不需要人工指定新生代的大小(-Xmn)、Eden与Survior区的比例(-XX:SurviorRatio )、晋升老年代对象大小(-XX:PretenureSizeThreshold)等细节参数。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大吞吐量。这种调节方式称为垃圾收集的自适应的调整策略

工作区域

作用域新生代,采用标记-复制算法进行垃圾收集,会有STW,多线程并行进行垃圾回收

搭配使用

  • Parallel Scavenge 回收新生代 + Parallel Old 回收老年代
  • Parallel Scavenge 回收新生代 + Serial Old 回收老年代

应用场景

  • 用于用户交互或需要保证服务响应质量的程序

Serial Old 收集器

工作原理

Serial Old收集器是Serial收集器的老年代版本,它同样是一个单线程收集器,采用的是标记-整理算法。这个收集器的主要意义也是共客户端模式下的HotSpot虚拟机使用。
在服务端模式下,其可能有两种用途

  • 在JDK5以及之前的版本中与Parallel Scavenge收集器搭配使用
  • 作为CMS收集器发生失败时的后备预案。
    在这里插入图片描述

工作区域

作用于老年代,采用标记整理算法,会出现STW,单线程收集。

搭配使用

  • Serial回收新生代 + Serial Old 收集老年代
  • ParNew回收新生代 + Serial Old 收集老年代
  • Parallel Scavenge 回收新生代 + Serial Old 收集老年代
  • 作为CMS回收失败的后备方案。

Parallel Old收集器

工作原理

Parallel Old是Parallel Scavenge收集器的老年代收集版本,支持多线程并发收集,基于标记-整理算法实现。这个收集器是直到JDK6时才开始提供。
在这里插入图片描述

工作区域

作用于老年代,采用标记-整理算法,会出现STW,多线程并行收集。

搭配使用

  • Parallel Scavenge 回收新生代 + Parallel Old 收集老年代

CMS收集器

工作原理

CMS收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网网站或者基于B/S系统的服务端上,这类应用通常都会较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验。

其是基于标记-清除算法实现的,运作过程分为四个步骤:

  1. 初始标记
  2. 并发标记
  3. 重新标记
  4. 并发清除

其中初始标记重新标记这两个步骤依然需要STW,初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快。 并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发执行。重新标记阶段是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一点,但也远比并发标记阶段的时间短。最后是并发清除阶段,清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。

由于整个过程中耗时最长的并发标记和并发清除阶段中,垃圾收集器都可以与用户线程一起工作,所以总体来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
在这里插入图片描述

工作区域

作用域老年代,并发收集,标记清除算法,会有短暂的STW

搭配使用

  • ParNew回收新生代 + CMS收集老年代(Serial Old收集器作为备用)
  • Serial回收新生代 + CMS收集老年代(Serial Old收集器作为备用) 【JDK9取消这一组合

应用场景

  • 关注响应速度的互联网服务

优缺点

优点

  • 并发收集、低停顿

    缺点

  • CMS收集器对处理器资源非常敏感。在并发阶段,他虽然不会导致用户线程停顿,但却会因为占用了一部分处理器的计算能力而导致应用程序变慢,降低总吞吐量。CMS默认启动的回收线程数是(处理器核心数量 + 3) / 4。如果处理器核心数在四个及以上,并发回收时垃圾收集线程只占用不超过25%的处理器运算资源,并且会随着核心数量的增加而下降。但是当处理器核心数量不足四个时,CMS对用户程序的影响就可能变得很大。
  • 无法处理“浮动垃圾”,有可能出现“Con-current Mode Failure”失败进而导致另一次完全的STW的Full GC产生。在CMS的并发标记和并发清理阶段,用户线程还在继续运行,自然就会有新的垃圾对象产生,但这一部分垃圾在当次收集中无法处理,只好留在下一次垃圾收集时清理。这部分垃圾称为“浮动垃圾” 同样是因为在垃圾收集阶段用户线程还需要继续运行,那就还需要预留足够的内存空间提供给用户线程使用,因此CMS收集器不能像其他收集器那样等待到老年代几乎被填满再进行收集,需要预留一部分空间供并发收集时的程序运作使用。可以通过参数-XX:CMSInitiatingOccu-pancyFraction的值来设置CMS的触发百分比。当此参数较低时,虽然可以保证并发阶段新对象的内存分配,但是增加了老年代的回收频率。当此参数较高时,虽然降低了回收频率,但是如果当用户线程无法分配内存时,就是暂停用户线程,启用备用方案即Serial Old收集器进行垃圾收集。所以此参数得根据自己的使用场景进行均衡。
  • CMS是基于标记-清除算法来实现的。那么就会产生内存碎片,当内存碎片过多时,对于分配大对象的场景就会带来麻烦,往往老年代的空闲空间足够分配,但由于内存不是连续的导致无法分配大对象,从而导致不得不触发一次Full GC为了解决这个问题,CMS提供了-XX:+UseCMS-CompactAtFullCollection开关参数,用于在CMS收集器不得不进行Full GC时开启内存碎片的合并整理过程,由于内存整理的过程必须移动存活对象是无法并发的。这样空间碎片的问题是解决了,但停顿时间又会变长。因此虚拟机的设计者还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction。这个参数作用是要求CMS收集器在执行若干次不整理空间的Full GC之后,下一次进入Full GC前后会先进行碎片整理。

Garbage Firse(G1)收集器

工作原理

G1是一款主要面向服务端的垃圾收集器。作为CMS收集器的替代者和继承人,设计者们希望做出一款能够建立起“停顿时间模型”的收集器,停顿时间模型的意思是能够支持指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间大概率不超过N毫秒这样的目标

G1可以面向堆内存任何部分组成会收集(Collection Set 一般简称为Cset)进行回收,衡量标准不再是它属于那个分代,而是那块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式

G1开创的基于Region的堆内存布局是它能够实现这个目标的关键。虽然G1也仍是遵循分代收集理论设计的,但其堆内存的布局与其他收集器有非常明显的差异。G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活一段时间、熬过多次收集的旧对象都能获取很好的收集效果。

Region中还有一类特殊的Humongous区域,专门用来存储大对象,G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围为1MB~32MB,且应为2的N次幂。对于那些超过了整个Region容量的超级大对象,将会被存放在N个连续的Humongous之中,G1的大多数行为都把Humongous Region作为老年代的一部分来看待。

虽然G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,他们都是一系列区域(不需要连续)的动态集合。G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作为单次回收的最小单元,即每次收集到的内存空间都是Region大小的整数倍,这样可以有计划的避免在整个Java堆中进行全区域的垃圾收集。更具体的处理思路是让G1收集器去跟踪各个Region里面的垃圾堆积的”价值“大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间,优先处理回收价值收益最大的Region。这种使用Region划分内存空间,以及具有优先级的区域回收方式,保证了G1收集器在有限的时间内获取尽可能高的收集效率。
在这里插入图片描述

需要解决的问题:

  • 将堆分成多个独立的Region之后,Region里面存在的跨Region引用对象如何解决?,解决的思路是利用:记忆集避免全堆作为GC Roots扫描,但在G1收集器上记忆集的应用实际要复杂很多。每个Region都维护有自己的记忆集,这些记忆集会纪录下别的Region指向自己的指针,并标记这些指针分别在那些卡页的范围之内。G1的记忆集在存储结构的本质是一种哈希表,Key是别的Region的起始地址,Value是一个集合,里面存储的元素是卡表的索引号。这种“双向”的卡表结构(卡表是我指向谁,这种结构还纪录了谁指向我) 比原来的卡表实现起来更复杂,同时由于Region数量比传统收集器的分代数量明显要多得多,因此G1收集器要比其他的传统垃圾收集器有着更高的内存占用负担。根据经验,G1至少要耗费大约相当于Java堆容量的10%-20%的额外内存来维持收集器的工作

  • 在并发标记阶段如何保证收集线程与用户线程互不干扰运行? 。这里首先要解决的是用户线程改变的对象引用关系时,必须保证其不能打破原本的对象图结构,导致标记结果出现错误。CMS收集器采用增量更新算法实现,而G1收集器则是通过原始快照(SATB)算法来实现的

-------------本文结束,感谢您的阅读!-------------