深入理解Java虚拟机 --- 垃圾回收器

Serial收集器

HotSpot虚拟机运行在客户端模式下的默认新生代收集器。

类型:单线程串行垃圾回收器

垃圾收集算法:复制算法

作用区域:新生代

特点:

1、只会用单个线程去完成垃圾收集工作,用户线程会STW,直到收集结束。

2、没有线程交互,专心做垃圾收集,获得最高的单线程收集效率。

ParNew收集器

类型:多线程并行垃圾回收器

垃圾收集算法:复制算法

作用区域:新生代

本质上就是Serial收集器的并行版本。

特点:

1、能与CMS收集器搭配使用。

Parallel Scavenge收集器

类型:多线程并行垃圾回收器

垃圾收集算法:复制算法

作用区域:新生代

特点:

1、是以吞吐量优先的垃圾回收器。(吞吐量=用户运行代码时间/用户运行代码时间+垃圾收集时间)

2、不能与CMS收集器搭配使用。

3、内置有一个 PS MarkSweep收集器,但是实现原理跟Serial实现基本一样。

为什么CMS只能和ParNew搭配使用,而不能和Parallel Scavenge搭配使用?

就性能来看,Parallel Scavenge它的性能会比ParNew的性能要好一些。

但是CMS只能和ParNew搭配使用,原因如下:

1、CMS的设计目标是低延迟,Parallel Scavenge的设计目标是高吞吐量。

2、Parallel Scavenge没有使用HotSpot的分代框架,而CMS使用了HotSpot的分代框架。(ParNew使用了HotSpot的分代框架)

Serial Old收集器

类型:单线程串型收集器

垃圾收集算法:标记-整理算法

作用区域:老年代

Parallel Old收集器

类型:多线程并行收集器

垃圾收集算法:标记-整理算法

作用区域:老年代

特点:

1、缓解了Parallel Scanvenge的尴尬局面。(在其出之前Parallel Scanvenge只能和Serial搭配使用)

CMS收集器

这款收集器是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。

CMS收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间。停顿时间越短(低延迟)就越适合与用户交互的程序,良好的响应速度能提升用户体验。

类型:多线程并发收集器

垃圾收集算法:标记-清除算法

作用区域:老年代

工作原理

image.png

  • 初始标记阶段:所有工作线程都会因为"Stop-the-World"机制而短暂暂停,这个阶段主要任务仅仅只是标记出GC Roots能直接关联到的对象

  • 并发标记阶段:从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要用户线程停顿。扫描完成后,这个过程可能会造成对象引用的改变。CMS使用了增量标记的技术,把改变的都引用发起者(黑色对象)存储起来。

  • 重新标记阶段:主要是处理并发阶段中存储起来的黑色对象,在STW的前提下,重新以他们为根进行标记。

  • 并发清除阶段:这个阶段清除掉标记阶段判断已经死亡的对象,释放内存空间。这个阶段可以与用户线程并发。

为什么采用标记-清除算法?

因为在清除过程中,GC线程是跟用户并发的,如果使用复制或标记-整理算法会造成对象内存地址的改变,那么会造成用户持有原先的引用而无法访问到对象的情况。

特点

1、触发CMS垃圾回收器时需要预留一定的空间来支持用户创建对象。

触发的阙值我们可以设置,但是在极端情况下,如果设置阙值过高,造成无法满足程序分配对象,那么虚拟机就会被迫启动Serial Old收集器来进行收集,这就会造成长时间的STW了。

2、会产生浮动垃圾

3、进行Full GC时,会进行内存碎片的整理

4、CMS对CPU资源非常敏感,在并发阶段时,虽然不会造成用户线程的STW,但是会因为占有CPU资源,而导致应用程序变慢,降低总吞吐量。

G1垃圾收集器

具有跨时代意义的垃圾收集器,设计思想:局部收集、基于Region的内存布局形式

设计目的:回收垃圾最大化。

作用区域:整个堆

垃圾收集算法:从整体上来看是标记-整理算法。但从两个Region的角度来看,是标记-复制算法。

Region

image.png

G1保留了年轻代和老年代的概念,但新生代和老年代不再是固定的了,而是一系列不需要连续的动态集合。

其中,Homongous区域主要用来存放大对象。G1认为只要大小超过了一半的Region区域的对象就称为大对象。大多数情况下,G1把Homongous当成老年代看待。

设置H区域的原因

之前遇到大对象且年轻代Eden空间不够的话,那么会将大对象放到老年代,那么这个大对象得等到Old GC或者Full GC才能得以清理。

而设置H区之后:1、避免了内存碎片 2、提高了回收大对象的效率

工作原理

  • 初始标记阶段:所有工作线程都会因为"Stop-the-World"机制而短暂暂停,这个阶段主要任务仅仅只是标记出GC Roots能直接关联到的对象

  • 并发标记阶段:从GC Root开始对堆中对象进行可达性分析,这个过程与用户线程并发执行。扫描完成后,这个过程中可能会存在对象引用的改变,G1使用原始快照(STAB)方式来避免出现漏标的情况。

  • 重新标记阶段:主要处理并发标记过程中的STAB记录,在STW的前提下,以他们为根进行重新标记。

  • 清除阶段:这个阶段需要用户线程STW。这个阶段清除掉标记阶段判断已经死亡的对象,释放内存空间。(使用的是复制算法)

问题

跨Region的引用问题:

当最开始我们使用分代模型的时候,只需要注意跨代引用,我们解决的思路是通过记忆集统计老年代到年轻代的引用即可。

而跨Region,我们就需要记录得跟复杂了。解题思路还是通过记忆集来记录跨Region引用,通过空间换时间的思路来避免全堆作为GC Root扫描。但是这个记忆集就需要每个Region都去维护自己独特的一个了,每个Region的记忆集不仅要记录我引用了谁,也要记录谁引用了我。

所以,记忆集造成了比较大的空间浪费。(10%~20%的堆空间浪费)

并发标记阶段如何避免漏标问题:

漏标:把存活的对象给他误删了。

G1使用了原始快照来解决漏标问题。当灰色对象要解开和白色对象的引用时,把白色对象记录下来。并在后面以他们为根,去扫描,让他们存活。(思路:就是以可能存在浮动垃圾的下,减少扫描的时间。)

Young GC & Mixed GC

G1通过构造一个可预测的时间模型,来在特定的时间内收获最高的垃圾。

那么在收集时,就不会只局限于年轻代了,当涉及多个代时,我们称为Mixed GC。

当只涉及年轻代时,我们还是称为Young GC。

ZGC垃圾收集器

概述

ZGC是JDK11推出的低延迟垃圾回收器。

设计目标:

1、停顿时间不超过10ms(低停顿)

2、停顿时间不会随着堆大小或者活跃对象的大小而增加。

适用场景:大内存低延迟服务

前者GC的痛点

前者的GC在STW这块都不是特别友善,所以在一些低延迟服务造成了性能困扰。

CMS(ParNew)与G1停顿时间瓶颈

ParNew和G1使用的都是标记-复制算法。

瓶颈定位→复制阶段中的转移阶段中复制对象。

复制阶段中转移阶段是STW的,转移阶段需要分配新内存和复制对象的成员变量。其中内存分配比较快,但是在复制一些复杂对象的时候耗时会比较长。

为什么转移阶段不能和用户并发执行呢?

主要是无法解决转移过程中精确定位对象地址的问题。如果并发执行,那么会导致用户访问不到具体对象。

ZGC的原理

全并发

ZGC采用标记-复制算法。不过ZGC在标记、转移和重定位阶段几乎都是并发的。

image.png

ZGC只有三个STW阶段:初始标记,再标记,初始转移。其中,初始标记和初始转移分别都只需要扫描所有GC Roots,其处理时间和GC Roots的数量成正比,一般情况耗时非常短;再标记阶段STW时间很短,最多1ms,超过1ms则再次进入并发标记阶段。即,ZGC几乎所有暂停都只依赖于GC Roots集合大小停顿时间不会随着堆的大小或者活跃对象的大小而增加。与ZGC对比,G1的转移阶段完全STW的,且停顿时间随存活对象的大小增加而增加

ZGC的技术

着色指针

image.png

着色指针是一种将信息存储在指针中的技术。

当应用程序创建对象时,首先在堆空间申请一个虚拟地址。同时会在M0、M1、Remapped地址空间分别申请一个虚拟地址,且三个虚拟地址对应同一个物理地址但这三个虚拟地址在同一时间只有一个空间有效

读屏障

Object o = obj.FieldA   // 从堆中读取引用,需要加入屏障
<Load barrier>
Object p = o  // 无需加入屏障,因为不是从堆中读取引用
o.dosomething() // 无需加入屏障,因为不是从堆中读取引用
int i =  obj.FieldB  //无需加入屏障,因为不是对象引用

ZGC中读屏障的代码作用:在对象标记和转移过程中,用于确定对象的引用是否满足条件,并做出对应的动作。

ZGC的并发处理过程

1、初始化:ZGC初始化之后,整个内存空间的地址视图被设置成Remapped。程序正常运行,在内存中分配对象,满足一定条件后垃圾回收启动,进入标记阶段。

2、并发标记阶段:第一次进入标记阶段时视图为M0,如果对象被GC标记线程或者应用线程访问过,那么就讲对象的地址视图从Remapped调整为M0。所以,在标记阶段之后,如果在M0就说明对象是活跃的,如果是在Remapped就说明对象是不活跃的。

3、并发转移阶段:标记结束后就进入转移阶段,此时地址视图再次被设置为Remapped,如果对象被GC标记线程或者应用程序访问过,那么会从M0调整为Remapped。

其实,在标记阶段存在两个地址视图M0和M1,上面的过程显示只用了一个地址视图。之所以设计成两个,是为了区别前一次标记和当前标记。也即,第二次进入并发标记阶段后,地址视图调整为M1,而非M0。

着色指针和读屏障技术不仅应用在并发转移阶段,还应用在并发标记阶段:将对象设置为已标记,传统的垃圾回收器需要进行一次内存访问,并将对象存活信息放在对象头中;而在ZGC中,只需要设置指针地址的第42~45位即可,并且因为是寄存器访问,所以速度比访问内存更快。

posted @   ayu0v0  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示