【Java虚拟机】三色标记、增量更新、原始快照、记忆集与卡表

devtools/2024/9/22 21:48:00/

三色标记、增量更新、原始快照、记忆集与卡表

  • 三色标记
    • 基本原来
    • 错标、漏标
      • 错标
      • 漏标
  • 增量更新
    • 基本原理
    • 写屏障
  • 原始快照
    • 基本原理
    • 为什么G1使用原始快照而不用增量更新。
  • 记忆集与卡表

三色标记

基本原来

三色标记是JVM的垃圾收集器用于标记对象是否存活的一种方法。

三色是指黑、白、灰三种颜色,用这三种颜色记录不同对象的状态。

  • 黑:被标记为黑色的对象,表示该对象已经被GC线程扫描过,并且它所有引用的对象都被扫描过。
  • 白:白色对象是所有对象一开始的颜色,表示该对象还未被GC线程扫描到。
  • 灰:被标记为灰色的对象,表示该对象已经被GC线程扫描过,但是它还有引用的对象没有被扫描到。
    在这里插入图片描述

GC垃圾搜集线程一旦完成遍历,堆内存中要么就是黑色的对象,要么就是白色的对象。如果是黑色的对象,表示它可以通过GC Roots的引用链访问到;如果是白色的对象,表示无法通过GC Roots的引用链访问到。

在这里插入图片描述

那么,此时GC垃圾收集线程就可以把白色对象视作垃圾对象把它们清理掉,释放它们占用的内存空间。

在这里插入图片描述

这样,看起来一切正常,但这是建立在GC进行垃圾收集时用户线程停顿的前提下,也就是有STW的前提下。如果是并发标记,也就是用户线程在GC进行对象图遍历标记存活对象的时候,用户线程继续运行,就会出现错标和漏标的问题。

错标、漏标

错标

错标就是多标,也就是这个对象是个垃圾对象,但是GC把它标记为存活对象。

这种现象一般是由于GC垃圾收集线程已经把一个对象遍历到并标记为灰色或者黑色,然后用户线程把到该对象的引用链断开了。

一个对象被标记为黑色或者灰色,那么该对象就会被认定为存活对象。然后用户线程才把到该对象的引用链断开,此时GC垃圾收集线程是不知道的,GC垃圾收集线程对于已经遍历过的对象,是不会再遍历的,因此该对象就会被当成存活对象被保留下来。但由于引用链已经断开,无法再访问到该对象,因此实际上该对象是个垃圾对象,是应该被回收的,因此这种现象就叫错标或者多标,也就是这个标记是多余的。

在这里插入图片描述

这种问题好解决,只要到下一次垃圾收集,还是会作为白色对象被回收的。

漏标

漏标是指本该被标记为存活的对象,但是GC没有标记,导致该对象被回收,此时用户线程访问该对象,就会发生空指针异常。

这种现象的发生必须满足两个条件:

  1. 一个灰色对象断开了对一个白色对象的引用。
  2. 一个黑色对象又建立对这个白色对象的应用。

在这里插入图片描述

在GC完成剩下的对象的遍历之后,GC就会把它作为白色对象把它回收掉。

在这里插入图片描述
如果用户线程通过这个空指针引用去访问这个被GC回收掉的对象,就会发生空指针异常,这显然是一个很严重的问题。

解决这个问题的方法就是增量更新和原始快照。

增量更新

基本原理

增量更新是CMS用于解决漏标的处理方式。也就是当黑色对象建立对白色对象的引用时,把该黑色对象标记为灰色。

在这里插入图片描述

然后在并发标记阶段结束之后,在重新标记阶段,从新从该灰色对象开始进行深度优先遍历。

在这里插入图片描述

这样,GC就可以把该白色对象标记为黑色,就不会被GC当成垃圾对象被回收。

但是把黑色对象修改为灰色的这个操作如何实现的呢,这肯定不是由GC垃圾收集线程完成的,因为它是不会在并发标记阶段重新扫描已经扫描过的对象的。

写屏障

实际上这个操作是通过写屏障实现,写屏障就是在指针赋值操作前后插入一段额外的逻辑。

在这里插入图片描述

增量更新是在黑色对象建立到白对象引用的之后,把该黑色对象标记为黑色的,因此自然是把写屏障插入到指针赋值操作的后面。

在这里插入图片描述

然后在重新标记阶段,GC就可以从该灰色对象开始,重新进行扫描。

在这里插入图片描述

原始快照

基本原理

G1垃圾收集器解决漏标的办法则是原始快照,原始快照是在灰色对象断开对白色对象的引用时,把被删除的灰色对象到白色对象的引用记录下来,把白色对象修改为灰色。

在这里插入图片描述

这样GC就可在最终标记阶段通过该快照引用继续扫描到该白色对象。

在这里插入图片描述

由于是在灰色对象断开对白色对象的引用前,把该引用作为快照保存下来,因此是在指针赋值操作前插入的写屏障。

在这里插入图片描述

为什么G1使用原始快照而不用增量更新。

我个人觉得,G1之所有使用的是原始快照而不是增量更新,是因为原始快照比起增量更新来说,在最终标记阶段需要扫描的对象更少。

我们根据上面的例子,看看增量更新需要重新扫描的对象:在这里插入图片描述

然后再看看使用原始快照,需要扫描多少个对象:
在这里插入图片描述

可以看到,使用增量更新,需要扫描三个对象。而使用原始快照,只需要重新扫描1个对象。

这个例子只有少量几个对象,如果在真实场景下,对象的数量会非常非常多,这样性能差距就很明显了。并且G1它是面向大内存的垃圾收集器,使用原始快照就会比起使用增量更新来说少扫描很多的对象。

记忆集与卡表

JVM的垃圾收集分为年轻代与老年代两个区域,年轻代的对象朝生夕死,老年代的对象存活的时间较长。因此JVM对年轻代的垃圾收集频率肯定是比老年代的垃圾收集频率要高的。

但是在JVM中存在跨代引用的现象,也就是老年代的对象引用了年轻代的对象,或者是年轻代的对象引用了老年代的对象。但是JVM在进行年轻代的垃圾收集时,只会对年轻代进行扫描,不会扫描老年代,那么如果一个年轻代对象仅存在老年代对象对它的引用时,GC是扫描不到它的,自然就不会被标记为存活对象。

在这里插入图片描述

那么该对象就会被当成垃圾对象回收,当我们通过老年代对象指向该年轻代对象的引用访问该年轻代对象时,就会发生空指针异常。

在这里插入图片描述

因此GC在进行年轻代的垃圾收集时,不能只扫描年轻代的对象,还要扫描老年代中存在跨代引用的对象。如果直接把整个老年代都扫描的话,年轻代GC的性能就太低了,因此JVM定义了一个记忆集(Remember Set),记录非收集区到收集区的引用。比如现在要对年轻代进行垃圾收集,那么此时年轻代区域就是收集区,由于此时不需要对老年代进行垃圾收集,所以老年代就是非收集区。

在这里插入图片描述

记忆集只是一个抽象的概念,不同的Java虚拟机对记忆集有不同的实现,比如Hotspot虚拟机对记忆集的实现就是卡表。

首先Hotspot把内存划分为一小块一小块的区域,这些区域叫做卡页,然后定义一个字节数组CART_TABLE[],这个字节数组就是卡表,卡表中的每一个byte,对应内存中的一个卡页。

在这里插入图片描述

如果老年代中的一个对象引用了年轻代中的一个对象,那么该老年代对象所在的卡页对应在卡表中的那一个byte就会被修改为1,表示该卡页脏了。

在这里插入图片描述

那么当JVM要进行年轻代的垃圾收集时,通过CART_TABLE即可得知老年代区域中哪些卡页是脏页,就会扫描老年代中的脏页,把脏页中的对象加入到GC Roots中。

在这里插入图片描述

这样,被老年代对象引用的年轻代对象也可以被扫描到,就不会被当成垃圾对象被回收了。

这个修改卡表对应byte为脏的动作,也是通过写屏障触发的。

在这里插入图片描述


http://www.ppmy.cn/devtools/5186.html

相关文章

计算机网络——应用层(4)DHCP和套接字编程

一、动态主机配置协议DHCP 1、关于协议配置: 在协议软件中,给协议参数赋值的动作就叫协议配置一个协议软件在使用前必须已被正确配置,具体的配置信息取决于协议栈连接到互联网的计算机的协议软件需要正确配置的参数包括①IP地址&#xff1b…

uniapp H5项目 获取接口的二进制流转化成图片url(base64)

如果你使用的是uniapp, 并且你从接口获取下来的数据长这样: 想要把取到的数据展示成图片,那么你可以这样做: // 这是我们的项目封装的请求方法const res await this.$api.getKaptcha({originResponse: true, // 这样写是为了在request那边特…

基于ThinkPHP框架开发的的站长在线工具箱网站PHP源码(可以作为流量站)

这是一套基于ThinkPHP框架开发的站长在线工具箱网站PHP源码,包含了多种在线工具,可以作为流量站使用。 项 目 地 址 : runruncode.com/php/19742.html 部署教程: 环境要求: - PHP版本需要大于等于7.2.5 - MySQL版…

TCP/IP协议—TCP

TCP/IP协议—TCP TCP协议TCP通信特点TCP技术概念TCP定时器 TCP头部报文TCP连接三次握手(建立连接)四次挥手(释放连接)连接状态 TCP协议 传输控制协议(TCP,Transmission Control Protocol)是一种…

Java 通过 SFTP 和 FTP 访问时相对路径引发的问题汇总

背景 常用的 Java SSH 操作工具包是 jsch ,FTP 工具包 commons-net ,本文将总结 Java 程序通过 SFTP 协议和 FTP 协议访问远程文件的过程中,需要注意的路径问题。 本文主要包括下面三个问题: FTPClient 获取当前用户根目录的方…

python中isinstance()作用

Python 中的 isinstance() 函数具有以下作用 (类似Java 中的 instanceof ) 类型检查: isinstance() 用于判断一个对象是否属于特定的类型(或类型集合)。它接受两个参数:一个是待检测的对象,另一…

【Redis】string数据类型

文章目录 常用命令setsetnx & NXXXsetex & EXpsetex & PX msetget & mgetincr & decrincrby & decrbyincrbyfloatappendgetrangesetrangestrlen 内部编码 字符串类型是 Redis 最基础的数据类型。 在redis中所有的键都是 string 类型,其他的…

【C++】:函数重载,引用,内联函数,auto关键字,基于范围的for循环,nullptr关键字

目录 一,函数重载1.1 函数重载的定义1.1.1.形参的类型不同1.1.2参数的个数不同1.1.3.参数的顺序不同1.1.4.有一个是缺省参数构成重载。但是调用时存在歧义1.1.5.返回值不同,不构成重载。因为返回值可接收,可不接受,调用函数产生歧…