JVM垃圾收集算法与垃圾回收器CMS&G1

大纲

这篇文章应该发在ZGC之前的,不过由于一些原因,现在重新补发一下。
学完这篇文章,你将学到一些常见的垃圾回收算法,以及详细解释CMS与G1垃圾回收器的运行机制,话不多说,先上脑图~

在这里插入图片描述

在这里插入图片描述

什么是垃圾

B b = new B(); --创建了一个B对象
b = null; --堆内B对象则成为了垃圾
每个线程在运行时会创建一个虚拟机栈,每个方法运行时会封装成一个栈帧对象,包含了:

  • 局部变量表
  • 操作数栈
  • 动态链接
  • 完成出口
public class GCRoots {

    public Object Object = null;//是引用,它不是根

    public static Object so = new Object();//静态变量

    public final static Object fo = new Object();//常量

    public static void main(String[] args){
        Object o1 = new Object(); //o1局部变量(方法执行完成时出虚拟机栈,o1可回收)
        o1.hashCode();//JNI的指针(native方法中入参和出参,有指针)
    }

    public static void none(){
        GCRoots c = new GCRoots();//c是根
        c = null;//把与GCRoots对象的根 切断了
    }

}

JVM进行垃圾回收本质:回收堆内存,垃圾会自动回收

垃圾回收、JVM早期、python采用引用计数法,记录对象引用次数,因为循环引用的存在,因此需要做额外处理,因此垃圾回收算法不如java

@Data
public class GCRoots {

    private Object instance;

    public static void each(){
        GCRoots A = new GCRoots();
        GCRoots B = new GCRoots();

        A.instance = B;
        B.instance = A;

        A = null;
        B = null;
    }

}

GC roots有哪些?讲一讲可达性分析算法

GC roots即GC根的集合,包含四种根:

  • 静态变量
  • 局部变量
  • 常量
  • JNI(指针)

常见的垃圾回收算法

复制算法

  • 主要用于年轻代
  • 实现简单、运行高效
  • 没有内存碎片
  • 内存空间利用率只有一半

标记清除与标记整理算法

在这里插入图片描述

标记清除算法:

  • 存在内存碎片
  • CMS采用:因为并发清理时,不允许对象移动
  • 不需要STW

标记整理算法:

  • 标记
  • 整理
  • 清除
  • Serial Old/Parallel Old/G1采用
  • 必须要STW

对象分配原则/堆内存管理

image.png

  • JVM将堆内存分为新生代与老年代,新生代又分为Eden,from,to,也可称为S0,S1
  • 当对象分配空间时,优先分配到新生代的Eden区
  • 发生young gc,eden区存活对象O1进入from区,age为1
  • 当发生第二次yong gc(扫描Eden+from区),eden区存活对象O2进入to区,age为1,O1也进入to区,age为2
  • 第三次扫描区域为Eden区+to区,对象进入from区,age+1
  • 多次gc过后,当对象age=15时,进入old区

新生代回收方式:
S0S1采用了复制回收算法
appel式回收,空间利用率达到90%
依据原则:绝大部分对象朝生夕死

例外:

  • 对象过大时,From或者To区放不下,直接进入老年代

对象进入老年代

大对象直接进入老年代

Serial与ParNew垃圾回收器可以通过参数控制:-XX:PretenureSizeThreshold=4M 参数来控制

长期存活的对象进入老年代

对象在Eden每经历一次Minor GC,分代对象年龄+1,当增加到一定程度(默认15岁,CMS默认6岁),就会晋升到老年代,可以通过参数 -XX:MaxTenuringThreshold 来设置

动态年龄判断

Survivor区有一批对象,年龄1+年龄2+…+年龄n的多个年龄对象总和超过了Survivor区域的50% ,此时就会把年龄n(含)以上的对象都放入老年代。这个规则是希望那些可能长期存活的对象,尽早进入老年代

老年代空间分配担保机制

image.png

垃圾回收器

image.png

Serial收集器

  • -XX:+UseSerialGC -XX:+UseSerialOldGC
  • 全程单线程,新生代采用了复制算法,老年代采用了标记-整理算法

image.png

Parallel Scavenge收集器

  • -XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代)
  • JDK1.8默认收集器,即PS组合
  • Parallel收集器其实就是Serial收集器的多线程版本,吞吐量优先,STW时间较长
  • 新生代采用复制算法,老年代采用标记-整理算法

image.png

ParNew收集器

  • -XX:+UseParNewGC
  • ParNew收集器其实跟Parallel收集器很类似,区别主要在于它可以和CMS收集器配合使用
  • 新生代收集器,采用复制算法
    image.png

CMS收集器

简介
  • -XX:+UseConcMarkSweepGC(old)
  • 第一款并发收集器,实现垃圾收集线程和用户线程同时工作,响应时间优先
  • 老年代收集器,采用标记清除算法
工作流程

image.png

工作过程如下:
  • 初始标记:标记一下GC Roots能直接关联的对象,时间短,STW
  • 并发标记:进行GC Roots跟踪的过程,时间长,并发
  • 重新标记:上一步中标记产生变动的那一部分对象,三色标记的增量更新算法,时间短,STW
  • 并发清除:清除GC Roots不可达对象,新增对象标记为黑色不作处理,时间最长,并发
  • 并发重置
CMS存在的问题:
  • CPU敏感:上下文切换耗费资源,核心数少的CPU遇上过多线程会导致吞吐量降低
  • 浮动垃圾:在并发标记和并发清理阶段又产生垃圾,这种浮动垃圾只能等到下一次gc再清理了
  • 内存碎片:标记清除算法导致
  • concurrent mode failure:并发标记和并发清理阶段,没有回收完再次触发full gc,会用serialold垃圾收集器替代,造成长时间STW
CMS处理内存碎片问题:

当内存碎片导致分配不了对象,采用Serial Old(标记整理)替代CMS,造成长时间STW
为了避免替换成Serial Old,因此可以定时重启(游戏服务器)

CMS核心参数
  1. -XX:+UseConcMarkSweepGC:启用cms
  2. -XX:ConcGCThreads:并发的GC线程数
  3. -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)
  4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一
  5. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)
  6. -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设
    定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整
  7. -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,目的在于减少老年代对年轻代的引用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在标记阶段
  8. -XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW
  9. -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;

三色标记

并发标记过程中,对象状态可能会变更,有两种情况

  • 多标:已经被标记为非垃圾的对象变为垃圾对象,影响不大,下次gc回收即可
  • 漏标:产生的浮动垃圾,靠三色标记+读写屏障解决

三种颜色如下:
黑色:代表所有对象已经被垃圾回收器访问过,且这个对象的所有引用都已经扫描过了
灰色:代表对象已经被垃圾收集器访问过,但这个对象至少有一个引用没有被扫描过
白色:尚未被垃圾收集器访问过。刚开始时,所有对象都为白色,若结束仍未白色代表不可达

写屏障一般有两种实现方式:(参考AOP的概念)

  • 增量更新(Incremental Update)
    黑色对象一旦插入了只想白色对象的引用之后,它就变成了灰色对象
  • 原始快照(Snapshot At The Beginning,SATB)
    将删除的引用记录下来,在重新扫描时,将白色对象标记为黑色。待下一轮gc处理

以Java HotSpot为例,其并发标记时漏标的处理方案如下:

  • CMS:写屏障+增量更新
  • G1,Shenandoah:写屏障+SATB
  • ZGC:读屏障

G1回收器

image.png

G1介绍
  • G1将Java堆划分成了大小相等的独立区域(Region
  • 一般Region大小等于堆大小处于2048,比如堆大小4096M,则Region大小为2M
  • G1保留了年轻代和老年代的概念,但是不再物理隔阂了
  • 年轻代默认占比5%,运行过程中会不断增加更多Region,最多60%
  • Eden与Survivor区比例仍然为8:1:1
  • Humongous区专门保存大对象,可以横跨多个Region,Full GC时,一并回收
G1流程

image.png

  • 初始标记:同CMS
  • 并发标记:同CMS
  • 最终标记:同CMS
  • 筛选回收:根据回收价值和成本进行排序,根据用户期望GC停顿时间制定回收计划
G1垃圾收集分类
  • YoungGC
    G1计算回收时间接近参数 -XX:MaxGCPauseMills 设定的值,那么就会触发Young GC
  • MixedGC
    老年代的堆占有率达到参数(-XX:InitiatingHeapOccupancyPercent)设定的值则触发,回收所有的 Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以及大对象区
  • FullGC
    停止系统程序,然后采用单线程进行标记、清理和压缩整理,好空闲出来一批Region来供下一次MixedGC使用,这 个过程是非常耗时的
高吞吐量系统如何优化

类似于kafka几十万并发场景,调整-XX:MaxGCPauseMills小一点可以减少卡顿

如何选择垃圾回收器

4G以下可以用parallel,4-8G可以用ParNew+CMS,8G以上可以用G1,几百G以上用ZGC

为什么G1用SATB?CMS用增量更新

  • SATB相对增量更新效率更高(当然SATB会造成更多的浮动垃圾),G1不怕浮动垃圾
  • 增量更新需要进行深度扫描,G1分区太多,扫描时间过长

记忆集与卡表

主要解决老年代引用新生代对象的问题,卡表是记忆集在hotspot中的具体实现
卡表为一个字节数组,每个元素对应着一小块内存区域(起始值),称为“卡页”,写屏障维护

安全点

安全点就是指代码中一些特定的位置,当线程运行到这些位置它的状态是确定的,这样JVM可以安全进行一些操作,比如GC等,所以GC不是什么时候做就立即触发的,是需要等待所有线程运行到安全点后才触发:

  1. 方法返回之前
  2. 调用某个方法之后
  3. 抛出异常的位置
  4. 循环的末尾

安全区域

Safe Region 是指在一段代码片段中,引用关系不会发生变化。在这个区域内的任意地方开始 GC 都是安全的,比如线程因为sleep中断。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值