垃圾收集器

垃圾收集器

  • Serial收集器
  • ParNew收集器
  • Parallel Scavenge收集器
  • Serial Old收集器
  • Parallel Old收集器
  • CMS收集器
  • G1垃圾收集器
  • ZGC垃圾收集器


垃圾收集算法 是内存回收的方法论,而垃圾收集器就是内存回收的具体实现。

我们不同分代之间是有着不同的收集器的,如果两个收集器之间存在连线,那就说明他们可以搭配使用
垃圾收集器_第1张图片

那么是如何在虚拟机中指定它使用上图中搭配的垃圾收集器呢?主要如下:

-XX:+UseSerialGC   新生代和老年代都用串行收集器,,即 Serial 和 Serial Old
-XX:+UseParNewGC   新生代使用 ParNew,老年代使用 Serial Old
-XX:+UseParallelGC   新生代使用 ParallerGC,老年代使用 Serial Old
-XX:+UseParallerOldGC   新生代使用 ParallerGC(即Parallel Scavenge),老年代使用 Parallel Old
-XX:+UseConcMarkSweepGC   表示新生代使用 ParNew,老年代的用 CMS
-XX:+UseG1GC   表示使用 G1


了解了上次的基本搭配,这里我们可以我们自己电脑JDK上是使用的哪一种垃圾收集器,可以使用这个命令

java -XX:+PrintCommandLineFlags -version

在这里插入图片描述



收集器 收集对象 算法 收集器类型 说明 适用场景
Serial 新生代 复制算法 单线程 进行垃圾收集时,必须暂停所
有工作线程,直到完成;
(stop the world)
简单高效;
适合内存不大的情况
ParNew 新生代 复制算法 并行的多线程收集器 ParNew垃圾收集器是Serial
收集器的多线程版本
搭配CMS垃圾回收器的首选
Parallel
Scavenge
吞吐量优先
新生代 复制算法 并行的多线程收集器 类似ParNew,更加关注吞吐
量,达到一个可控制的吞吐量
本身是Server级别多CPU机
器上的默认GC方式,主要适
合后台运算不需要太多交
互的任务

吞吐量 = 运行用户代码时间/(运行用户代码时间+ 垃圾收集时间)
垃圾收集时间 = 垃圾回收频率 * 单次垃圾回收时间


收集器 收集对象 算法 收集器类型 说明 适用场景
Serial
Old
老年代 标记整理算法 单线程 jdk7/8默认的老生代垃圾回收器 Client模式下虚拟机使用
Parallel
Old
老年代 标记整理算法 并行的多线程收集器 Parallel Scavenge收集器的老年代版
本,为了配合Parallel Scavenge的面
向吞吐量的特性而开发的对应组合
在注重吞吐量以及CPU
资源敏感的场合采用
CMS 老年代 标记清除算法 并行与并发收集器 尽可能的缩短垃圾收集时用户
线程停止时间;缺点在于:
1、内存碎片 2、需要更多cpu资源
3、浮动垃圾问题,需要更大的堆空间
重视服务的响应速度、
系统停顿时间和用户体
验的>互联网网站或者
B/S系统。互联网后端目
前cms是主流垃圾回收器

并行: 垃圾收集的多线程的同时进行。
并发: 垃圾收集的多线程和应用的多线程同时进行。


收集器 收集对象 算法 收集器类型 说明 适用场景
G1 跨新生代和老年代 标记整理+化整为零 并行与并发收集器 JDK1.7才正式引入,采用分区回
收的思维,基本不牺牲吞吐量的
前提下完成低停顿的内存回收;
可预测的停顿是其最大的优势
面向服务端应用
的垃圾回收器,
目标为取代CMS
ZGC 跨新生代和老年代 动态地创建和销毁
Region;动态地决
定Region的大小
并发,只会在枚举
根节点的阶段STW
(Stop The World),
因此停顿时间不会
随着堆大小或存活
对象的多少而增加
JDK11版本包含一个全新的垃圾
收集器ZGC,它由Oracle开发,
承诺在数TB的堆上具有非常低的
暂停时间。
面向服务端应用
的垃圾回收器,
目标为取代G1

|ZGC|跨新生代和老年代|动态地创建和销毁Region动态地决定Region的大小|只会在枚举根节点的阶段STW, 因此停顿时间不会随着堆大小或存活对象的多少而增加|JDK11版本包含一个全新的垃圾收集器ZGC,它由Oracle开发,承诺在数TB的堆上具有非常低的暂停时间。||




这里我们先来介绍一下新生代的垃圾收集器,一般从新生代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。


Serial收集器

这个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。

虽然它在进行垃圾收集时,工作线程必须停止,但是它也是有一点优点的,比如在单个CPU的环境下,Serial收集器由于没有线程交互的开销,可以获得最高的单线程收集效率,还有就是在桌面应用的场景下,分配给虚拟机管理的内存一般来说不会很大,停顿时间可以控制在几十毫秒最多一百多毫秒以内。
垃圾收集器_第2张图片


ParNew收集器

ParNew收集器其实就是Serial收集器的多线程版本,处理使用多条线程进行垃圾收集之外,其余的行为几乎和Serial收集器一致。在多CPU的环境,停顿时间比Serial垃圾收集器短。
垃圾收集器_第3张图片


Parallel Scavenge收集器

Parallel Scavenge收集器和ParNew收集器的区别是,它是主要关注吞吐量的垃圾收集器,高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。

所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间 /(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

垃圾收集器_第4张图片

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

  1. -XX:MaxGCPauseMills: 参数允许的值是一个大于0的毫秒数,收集器将尽可能地保证内存回收花费的时间不超过设定值。不过大家不要认为如果把这个参数的值设置得稍小一点就能使得系统的垃圾收集速度变得更快,GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的:系统把新生代调小一些,收集300MB新生代肯定比收集500MB快吧,这也直接导致垃圾收集发生得更频繁一些,原来10秒收集一次、每次停顿100毫秒,现在变成5秒收集一次、每次停顿70毫秒。停顿时间的确在下降,但吞吐量也降下来了。
  2. -XX:GCTimeRatio: 参数的值应当是一个大于0且小于100的整数,也就是垃圾收集时间占总时间的比率,相当于是吞吐量的倒数。如果把此参数设置为19,那允许的最大GC时间就占总时间的 5%(即1/(1+19)),默认值为99,就是允许最大 1%(即1/(1+99))的垃圾收集时间。

另外Parallel Scavenge收集器还有一个参数 -XX:+UseAdaptiveSizePolicy ,这是一个开关参数,当这个参数打开之后,就不需要手工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调节策略。




介绍完了新生代垃圾收集器,让我们再来看看老年代的垃圾回收器,Full GC是清理整个堆空间。


Serial Old收集器

Serial Old收集器是Serial收集器的老年代版本,它同样是一个单线程的收集器,使用“标记-整理”算法。

这个收集器主要的意义就是在于给Client模式下的虚拟机使用。


如果在Server模式下,那么它还有两大作用:

  1. 在JDK1.5以及之前的版本与Parallel Scavenge收集器搭配使用
  2. 作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。

垃圾收集器_第5张图片


Parallel Old收集器

Parallel Old收集器是在Parall Scavenge收集器的老年代版本,使用了多线程和“标记-整理”算法。

在注重吞吐量以及CPU资源敏感的场合,可以优先考虑Parallel Scavenge加上Parallel Old收集器。

垃圾收集器_第6张图片

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。一般应用在互联网网站或者B/S系统的服务端上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。


CMS收集器是基于“标记-清除”算法实现的,它的运行过程相对于前面的几种更为复杂,如下:

  1. 初始标记   短暂,仅仅只是标记一下GC Roots能直接关联到的对象,速度很快。
  2. 并发标记   和用户的应用程序同时进行,进行GC RootsTracing的过程
  3. 重新标记   短暂,为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
  4. 并发清除

由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
垃圾收集器_第7张图片


当然我们的CMS垃圾收集器也是有一些缺点的,如我们上图,CMS收集器运行示意图可以看书,就是在我们CMS在并发清除阶段,用户线程还在运行着,伴随着程序的运行自然就还会新的垃圾不断产生,这一部分垃圾在出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时在处理,这一部分垃圾就成为浮动垃圾



另外就是由于CMS垃圾收集器中,我们的用户线程也是需要运行的,所以就需要预留有足够的内存空间给用户线程去使用,因此CMS收集器不能像其他收集器那样等老年代几乎完全被填满了在进行收集。在JDK1.5的默认配置下,CMS收集器当老年代使用了68%的空间后就会被激活,这是一个偏保守的设置,如果在应用中老年代增长不是太快,可以适当调高参数 -XX:CMSInitiatingOccupancyFraction 的值来提高触发百分比,以便降低内存回收次数从而获取更好的性能,在JDK 1.6中,CMS收集器的启动阈值已经提升至92%。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,

这时虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置得太高很容易导致大量“Concurrent Mode Failure”失败,性能反而降低。

所以我们从一开始的图中就可以看出在CMS收集器和Serial Old之间是有一条连线的,我们在Serial垃圾收集器中也说了,Serial有一种用途就是作为CMS的后备预案的,在并发收集发生Concurrent Mode Failure时使用。
垃圾收集器_第8张图片


另外还有就是CMS垃圾收集器采用的是“标记-清除”算法实现的,所以CMS收集结束后会有大量空间碎片产生。空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很大的空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。

所以为了解决这个问题,CMS收集器提供了一个参数 -XX:+UseCMSCompactAtFullCollection(默认就是开启的),用于在CMS收集器顶不住要进行FullGC时开启内存碎片的合并整理过程,内存整理的过程是无法并发的,空间碎片问题没有了,但停顿时间不得不变长。

还提供了 -XX:CMSFullGCsBeforeCompaction 参数是用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的(默认值为0,表示每次进入FullGC时都进行碎片整理)。




G1垃圾收集器

G1(Garbage First)特点:

  • 并行与并发: G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。
  • 分代收集: 与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
  • 空间整合: 与CMS的“标记—清理”算法不同,G1从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。
  • 内存布局: 在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。

垃圾收集器_第9张图片

我们先来看看G1对于新生代的回收
垃圾收集器_第10张图片

回收Eden区和survivor区,回收后,所有eden区被清空,存在一个survivor区保存了部分数据。老年代区域会增多,因为部分新生代的对象会晋升到老年代。



垃圾收集器_第11张图片
那么G1垃圾收集器的新生代主要发生在上图的哪一个阶段呢?主要是在发生在初始化阶段,在初始化阶段中仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,产生一个全局停顿,都伴随有一次新生代的GC。

然后再初始化阶段后,会有一个根区域的扫描,就是扫描survivor区可以直接到达的老年代区域。

然后就是并发标记阶段扫描和查找整个堆的存活对象,并标记。我们并发标记阶段是并发的,工作线程也是在运行的,伴随着程序的运行自然就还会新的垃圾不断产生,这里我们又会进行重新标记,会产生全局停顿,对并发标记阶段的结果进行修正。

标记完成后,就是独占清理,这里也会产生全局停顿,对GC回收比例进行排序,供混合收集阶段使用,混合收集就是在我们Region区域中进行部分收集,会优先收集垃圾比例较高的区域,另外混合收集还可能会进行新生代的垃圾收集。

最后就是并发清理,就是识别并清理完全空闲的区域,并发进行。另外在G1中也可能在回收完后也会发生内存不足的的情况,这时也可能进行Full GC回收。


G1垃圾收集器还为提供了几个常用的参数:

  • -XX:MaxGCPauseMillis   指定目标的最大停顿时间,G1尝试调整新生代和老年代的比例,堆大小,晋升年龄来达到这个目标时间。
  • -XX:ParallerGCThreads   设置GC的工作线程数量

ZGC垃圾收集器

关键技术:

  1. 有色指针(Colored Pointers)
  2. 加载屏障(Load Barrier)

目标:

  1. 支持TB级堆内存(最大4T)
  2. 最大GC停顿10ms
  3. 对吞吐量影响最大不超过15%

ZGC通过技术手段把STW(Stop The World)的情况控制在仅有一次,就是第一次的初始标记才会发生,这样也就不难理解为什么GC停顿时间不随着堆增大而上升了,再大我也是通过并发的时间去回收了。

另外就是ZGC也是像G1一样划分Region,但更加灵活,G1一开始就把堆划分成固定大小的Region,而ZGC 可以有2MB,32MB,N×2MB 三种Size Groups,动态地创建和销毁Region,动态地决定Region的大小。256k以下的对象分配在Small Page, 4M以下对象在Medium Page,以上在Large Page。所以ZGC能更好的处理大对象的分配。

和G1一样会做内存的压缩,CMS是Mark-Sweep标记过期对象后原地回收,这样就会造成内存碎片,越来越难以找到连续的空间,直到发生Full GC才进行压缩整理。ZGC和G1会将活着的对象都移动到另一个Region,整个回收掉原来的Region。

你可能感兴趣的:(JVM)