聊一聊GC(垃圾回收器)

垃圾回收器:
垃圾回收算法是理论垃圾回收器是实现

目前有如下几种垃圾回收器,连线描述了各个垃圾回收器应用在哪里,以及相互之间是否可以相互配合使用
在这里插入图片描述

新生代的这三个都是复制回收算法
区别在于具体的实现方式

  • 新生代收集器:Serial、ParNew、Parallel Scavenge
  • 老年代收集器:CMS、Serial Old、Parallel Old
  • 整堆收集器: G1

Serial 收集器
Serial收集器是最基本的、发展历史最悠久的收集器

特点:单线程、简单高效(与其他收集器的单线程相比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程手机效率。收集器进行垃圾回收时,必须暂停其他所有的工作线程,直到它结束(Stop The World)

Serial / Serial Old收集器运行示意图
在这里插入图片描述
适用於单核CPU,因为单核CPU下,单线程效率更高

ParNew收集器
ParNew收集器其实就是Serial收集器的多线程版本
除了使用多线程外其余行为均和Serial收集器一模一样(参数控制、收集算法、Stop The World、对象分配规则、回收策略等)

  • 特点:多线程、ParNew收集器默认开启的收集线程数与CPU的数量相同,在CPU非常多的环境中,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。
       和Serial收集器一样存在Stop The World问题

  • 应用场景:ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器,因为它是除了Serial收集器外,唯一一个能与CMS收集器配合工作的

ParNew/Serial Old组合收集器运行示意图如下:
在这里插入图片描述
适用于多核CPU

Parallel Scavenge 收集器(Java7默认)
Parallel :指的是并行,具体来说是垃圾回收器线程并行执行
与吞吐量关系密切,故也称为吞吐量优先收集器,关注的是全局的运行情况,所以没有具体的时间点

  • 吞吐量:执行用户代码时间/(执行用户代码时间+垃圾回收使用的时间)

  • 特点:属于新生代收集器也是采用复制算法的收集器,又是并行的多线程收集器(与ParNew收集器类似)。
    该收集器的目标是达到一个可控制的吞吐量。还有一个值得关注的点是:GC自适应调节策略(与ParNew收集器最重要的一个区别)

  • GC自适应调节策略:Parallel Scavenge收集器可设置-XX:+UseAdptiveSizePolicy参数。当开关打开时不需要手动指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等,虚拟机会根据系统的运行状况收集性能监控信息,动态设置这些参数以提供最优的停顿时间和最高的吞吐量,这种调节方式称为GC的自适应调节策略。

Parallel Scavenge收集器使用两个参数控制吞吐量:

  • XX:MaxGCPauseMillis 控制最大的垃圾收集停顿时间
  • XX:GCRatio 直接设置吞吐量的大小(0,100)
    如果XX:MaxGCPauseMillis设置的太小,例如1纳秒,那么会导致GC非常频繁

Serial Old 收集器
Serial Old是Serial收集器的老年代版本

  • 特点:同样是单线程收集器,采用标记-整理算法
  • 应用场景:主要也是使用在Client模式下的虚拟机中。也可在Server模式下使用

Server模式下主要的两大用途:
在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用
作为CMS收集器的后备方案,在并发收集Concurent Mode Failure时使用

Serial / Serial Old收集器工作过程图(Serial收集器图示相同):

在这里插入图片描述

Parallel Old 收集器
是Parallel Scavenge收集器的老年代版本
Parallel :指的是并行,具体来说是垃圾回收器线程并行执行
特点:多线程,采用标记-整理算法
应用场景:注重高吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge+Parallel Old 收集器。
Parallel Scavenge/Parallel Old收集器工作过程图:
在这里插入图片描述

CMS收集器(老年代默认)
CMS垃圾回收器的全称是Concurrent Mark-Sweep Collector,从名字上可以看出两点,一个是使用的是并发收集,第二个是使用的收集算法是Mark-Sweep(标记-清除)
一种以获取最短回收停顿时间为目标的收集器

  • 特点:基于标记-清除算法实现。并发收集、低停顿。
  • 应用场景:适用于注重服务的响应速度,希望系统停顿时间最短,给用户带来更好的体验等场景下。如web程序、b/s服务

CMS收集器的运行过程分为下列4步:

  1. 初始标记:标记GC Roots能直接到的对象。速度很快但是仍存在Stop The World问题。
  2. 并发标记:进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行。
  3. 重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。仍然存在Stop The World问题(做的是增量更新,速度很快)
  4. 并发清除:对标记的对象进行清除回收

CMS收集器的内存回收过程是与用户线程一起并发执行的
CMS收集器的工作过程图:
在这里插入图片描述
CMS收集器的缺点:
对CPU资源非常敏感
无法处理浮动垃圾,可能出现Concurrent Model Failure失败而导致另一次Full GC的产生(浮动垃圾就是在并发清除的时候,又产生新的垃圾)
因为采用标记-清除算法所以会存在空间碎片的问题,导致大对象无法分配空间,不得不提前触发一次Full GC

G1收集器(jdk 9默认)

一款面向服务端应用的垃圾收集器,是标记整理算法

G1(Garbage First)和CMS一样,也是关注最小时延的垃圾回收器,同样适合大尺寸堆内存的垃圾收集
G1最大的特点是引入分区的思路,是分区算法的实现,其将整个堆空间划分为连续的不同小区间, 每个小区间独立使用,独立回收。这样做的好处是可以控制一次回收多少个小区间 ,根据目标停顿时间,每次合理地回收若干个小区间(而不是整个堆),从而减少一次 GC 所产生的停顿

特点如下:

  • 并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿时间。部分收集器原本需要停顿Java线程来执行GC动作,G1收集器仍然可以通过并发的方式让Java程序继续运行。
  • 分代收集:G1能够独自管理整个Java堆,并且采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
  • 空间整合:G1运作期间不会产生空间碎片,收集后能提供规整的可用内存。
  • 可预测的停顿:G1除了追求低停顿外,还能建立可预测的停顿时间模型。能让使用者明确指定在一个长度为M毫秒的时间段内,消耗在垃圾收集上的时间不得超过N毫秒。

G1收集器大致可分为如下步骤:

  • 初始标记:仅标记GC Roots能直接到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象。(需要线程停顿,但耗时很短。)
  • 并发标记:从GC Roots开始对堆中对象进行可达性分析,找出存活对象。(耗时较长,但可与用户程序并发执行)
  • 最终标记:为了修正在并发标记期间因用户程序执行而导致标记产生变化的那一部分标记记录。且对象的变化记录在线程Remembered Set Logs里面,把Remembered Set Logs里面的数据合并到Remembered Set中。(需要线程停顿,但可并行执行。)
  • 筛选回收:对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。(可并发执行)

为什么说G1是关注最小时延的垃圾回收器呢?
因为G1在筛选回收的阶段,会根据用户设置的期望的GC停顿时间来定制回收计划,例如:
用户期望每次GC停顿时间不超过10ms,而在筛选回收之前的阶段,已经用去了8ms,那么G1就会选择回收部分对象,以保证停顿时间不超过10ms

G1收集器运行示意图:
在这里插入图片描述
g1不再区分老年代、年轻代这样的内存空间,这是较以往收集器很大的差异,所有的内存空间就是一块划分为不同子区域,每个区域大小为1m-32m,最多支持的内存为64g左右,且由于它为了的特性适用于大内存机器。

G1的内存模型
在这里插入图片描述
分区 Region
G1采用了分区(Region)的思路,将整个堆空间分成若干个大小相等的内存区域,每次分配对象空间将逐段地使用内存。因此,在堆的使用上,G1并不要求对象的存储一定是物理上连续的,只要逻辑上连续即可
G1的每个分区不会确定地为某个代服务,可以按需在年轻代和老年代之间切换
启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区

卡片 Card
在每个分区内部又被分成了若干个大小为512 Byte卡片(Card),卡片将会记录在全局卡片表中,分配的对象会占用物理上连续的若干个卡片,查找对象时便可通过记录卡片来查找该引用对象。每次对内存的回收,都是对指定分区的卡片进行处理

在这里插入图片描述

ZGC收集器(目前尚无法使用在企业级应用上)
fullGC只需要10ms

Minor GC 和 Full GC的区别

  • 新生代GC(Minor GC):Minor GC指发生在新生代的GC,因为新生代的Java对象大多都是朝生夕死,所以Minor GC非常频繁,一般回收速度也比较快。当Eden空间不足以为对象分配内存时,会触发Minor GC
    注意young GC一旦发生就是对整个新生代进行GC,而不是仅仅针对Eden区

  • 老年代GC(Full GC/Major GC):Full GC指发生在老年代的GC,出现了Full GC一般会伴随着至少一次的Minor GC(老年代的对象大部分是Minor GC过程中从新生代进入老年代),比如:分配担保失败。Full GC的速度一般会比Minor GC慢10倍以上。当老年代内存不足或者显式调用System.gc()方法时,会触发Full GC

如何选择合适的垃圾收集器

官方建议:

  • 优先调整堆的大小让服务器自己来选择
  • 如果内存小于100M,使用串行收集器
  • 如果是单核,并且没有停顿时间要求,使用串行或JVM自己选
  • 如果允许停顿时间超过1秒,选择并行或JVM自己选
  • 如果响应时间最重要,并且不能超过1秒,使用并发收集器 CMS 或者G1

设置JVM使用的垃圾回收器
(1)串行
-XX:+UseSerialGC
-XX:+UseSerialOldGC
(2)并行(吞吐量优先):
-XX:+UseParallelGC
-XX:+UseParallelOldGC
(3)并发收集器(响应时间优先)
-XX:+UseConcMarkSweepGC
-XX:+UseG1G

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章