JVM 有哪些垃圾回收算法?(中)

简介: 分代收集算法的设计原则:收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象经历垃圾收集过程的次数)分配到不同的区域之中存储。

HotSpot 回收算法实现


根节点枚举


判断对象是否被回收的算法有引用计数算法和可达性分析算法。其中可达性分析算法是根据GC Roots根节点作为起始点向下搜索引用链,找不到引用链则判定对象可回收。

可作为GC Roots根节点的对象主要是在全局性的引用(如常量、类静态属性)和执行上下文中(如栈帧中的本地变量表),现在的很多应用仅方法区就有数百兆,逐个检查里边的引用显然很耗费时间。


在HotSpot实现中,利用了空间换取时间,是使用一组OopMap的数据结构来完成的。类加载完成后,会把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中,在特定的位置(即安全点)使用OopMap记录下栈和寄存器哪些位置是引用,这样GC发生的时候就不用全部扫描了。


安全点


如果每条指令都生成OopMap那将需要大量的额外空间。此时在特定的位置即安全点使程序不是在所有的地方都停顿(Stop The World)下来开始GC,只有在到达安全点时才停顿开始GC。安全点的选定不能使GC等待时间过长也不能使GC频繁触发,如具有方法调用、循环跳转、异常跳转的指令才会有安全点。


另一个问题是GC发生时如何让所有的线程都跑到安全点附近停顿下来?有两种方案:


  • 抢先式中断,GC发生时首先让所有的线程都停顿下来,然后让还没到安全点的线程恢复,跑到安全点。几乎没有虚拟机采用这种方式响应GC。


  • 主动式中断,GC发生需要中断所有的线程时,不直接对线程操作而是设置一个中断标志,该标志和安全点位置重合,各线程执行时都会去轮询该中断标志,如果线程发现该标志为真时就自己中断挂起。


安全区域


程序运行时安全点可以完美的解决GC触发的问题,但是当程序不执行时即没有分配到CPU时间时,此时线程不能响应JVM的中断请求,JVM显然不可能等待线程分配到CPU时间跑到安全点时再开始GC。这是需要安全区域来解决问题。


安全区域是指一段代码段中引用关系不会发生变化,在该区域何时何地开始GC都是安全的。线程执行安全区域的代码块时会标识自己已经进入了安全区域,此时如果JVM发起GC线程不会再标志中断状态标识,线程离开安全区域时会检查GC枚举GC Roots根节点(或者是整个GC过程)是否已经完成,如果完成了就继续执行,没完成的话就等待GC完成回收任务收到可以离开的信号再离开安全区域。


记忆集和卡表


了为解决对象跨代引用所带来的问题,垃圾收集器在新生代中建立了名为记忆集(Remembered Set)的数据结构,用以避免把整个老年代加进GC Roots扫描范围。事实上并不只是新生代、老年代之间才有跨代引用的问题,所有涉及部分区域收集(Partial GC)行为的 垃圾收集器,典型的如G1、ZGC和Shenandoah收集器,都会面临相同的问题,因此我们有必要进一步理清记忆集的原理和实现方式,以便在后续章节里介绍几款最新的收集器相关知识时能更好地理解。


记忆集是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。如果我们不考虑 效率和成本的话,最简单的实现可以用非收集区域中所有含跨代引用的对象数组来实现这个数据结构,代码所示


Class RememberedSet {
  Object[] set[OBJECT_INTERGENERATIONAL_REFERENCE_SIZE]; 
}


这种记录全部含跨代引用对象的实现方案,无论是空间占用还是维护成本都相当高昂。而在垃圾收集的场景中,收集器只需要通过记忆集判断出某一块非收集区域是否存在有指向了收集区域的指针就可以了,并不需要了解这些跨代指针的全部细节。那设计者在实现记忆集的时候,便可以选择更为 粗犷的记录粒度来节省记忆集的存储和维护成本,下面列举了一些可供选择(当然也可以选择这个范围以外的)的记录精度:


  • 字长精度:每个记录精确到一个机器字长(就是处理器的寻址位数,如常见的32位或64位,这个 精度决定了机器访问物理内存地址的指针长度),该字包含跨代指针。


  • 对象精度:每个记录精确到一个对象,该对象里有字段含有跨代指针。


  • 卡精度:每个记录精确到一块内存区域,该区域内有对象含有跨代指针。


其中,第三种“卡精度”所指的是用一种称为“卡表”(Card Table)的方式去实现记忆集,这也是目前最常用的一种记忆集实现形式,一些资料中甚至直接把它和记忆集混为一谈。前面定义中提到记 忆集其实是一种“抽象”的数据结构,抽象的意思是只定义了记忆集的行为意图,并没有定义其行为的 具体实现。卡表就是记忆集的一种具体实现,它定义了记忆集的记录精度、与堆内存的映射关系等。 关于卡表与记忆集的关系,读者不妨按照Java语言中HashMap与Map的关系来类比理解。


卡表最简单的形式可以只是一个字节数组,而HotSpot虚拟机确实也是这样做的。以下这行代 码是HotSpot默认的卡表标记逻辑:


CARD_TABLE[this address >> 9] = 0;


字节数组CARD_TABLE的每一个元素都对应着其标识的内存区域中一块特定大小的内存块,这个 内存块被称作“卡页”(Card Page)。一般来说,卡页大小都是以2的N次幂的字节数,通过上面代码可 以看出HotSpot中使用的卡页是2的9次幂,即512字节(地址右移9位,相当于用地址除以512)。那如 果卡表标识内存区域的起始地址是0x0000的话,数组CARD_TABLE的第0、1、2号元素,分别对应了 地址范围为0x0000~0x01FF、0x0200~0x03FF、0x0400~0x05FF的卡页内存块。卡表与卡页对应示意图:


image.png


一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字段存在着跨代 指针,那就将对应卡表的数组元素的值标识为1,称为这个元素变脏(Dirty),没有则标识为0。在垃 圾收集发生时,只要筛选出卡表中变脏的元素,就能轻易得出哪些卡页内存块中包含跨代指针,把它 们加入GC Roots中一并扫描。


写屏障


HotSpot虚拟机里是通过写屏障(Write Barrier)技术维护卡表状态的,写屏障可以看作在虚拟机层面对“引用类型字段赋值”这个动作的AOP切面,在引用对象赋值时会产生一个环形(Around)通知,供程序执行额外的动作,也就是说赋值的 前后都在写屏障的覆盖范畴内。在赋值前的部分的写屏障叫作写前屏障(Pre-Write Barrier),在赋值 后的则叫作写后屏障(Post-Write Barrier)。HotSpot虚拟机的许多收集器中都有使用到写屏障,但直至G1收集器出现之前,其他收集器都只用到了写后屏障。


应用写屏障后,虚拟机就会为所有赋值操作生成相应的指令,一旦收集器在写屏障中增加了更新卡表操作,无论更新的是不是老年代对新生代对象的引用,每次只要对引用进行更新,就会产生额外的开销,不过这个开销与Minor GC时扫描整个老年代的代价相比还是低得多的。


除了写屏障的开销外,卡表在高并发场景下还面临着“伪共享”(False Sharing)问题。伪共享是处 理并发底层细节时一种经常需要考虑的问题,现代中央处理器的缓存系统中是以缓存行(Cache Line) 为单位存储的,当多线程修改互相独立的变量时,如果这些变量恰好共享同一个缓存行,就会彼此影 响(写回、无效化或者同步)而导致性能降低,这就是伪共享问题。


if (CARD_TABLE [this address >> 9] != 0)    
  CARD_TABLE [this address >> 9] = 0;


在JDK 7之后,HotSpot虚拟机增加了一个新的参数-XX:+UseCondCardMark,用来决定是否开启卡表更新的条件判断。开启会增加一次额外判断的开销,但能够避免伪共享问题,两者各有性能损耗,是否打开要根据应用实际运行情况来进行测试权衡。


相关文章
|
2月前
|
存储 算法 Oracle
极致八股文之JVM垃圾回收器G1&ZGC详解
本文作者分享了一些垃圾回收器的执行过程,希望给大家参考。
|
11天前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
46 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
2月前
|
存储 算法 Java
JVM自动内存管理之垃圾收集算法
文章概述了JVM内存管理和垃圾收集的基本概念,提供一个关于JVM内存管理和垃圾收集的基础理解框架。
JVM自动内存管理之垃圾收集算法
|
3月前
|
存储 监控 算法
(六)JVM成神路之GC基础篇:对象存活判定算法、GC算法、STW、GC种类详解
经过前面五个章节的分析后,对于JVM的大部分子系统都已阐述完毕,在本文中则开始对JVM的GC子系统进行全面阐述,GC机制也是JVM的重中之重,调优、监控、面试都逃不开的JVM话题。
|
3月前
|
Java
Jinfo 查看 jvm 配置及使用 Jstat 查看堆内存使用与垃圾回收
Jinfo 查看 jvm 配置及使用 Jstat 查看堆内存使用与垃圾回收
59 5
|
3月前
|
存储 算法 Java
JVM 垃圾回收算法与垃圾回收器
JVM 垃圾回收算法与垃圾回收器
40 3
|
3月前
|
监控 Java 运维
开发与运维收集问题之jstat命令查看JVM垃圾回收情况如何解决
开发与运维收集问题之jstat命令查看JVM垃圾回收情况如何解决
27 1
|
2月前
|
算法 Java 应用服务中间件
探索JVM垃圾回收算法:选择适合你应用的最佳GC策略
探索JVM垃圾回收算法:选择适合你应用的最佳GC策略
|
3月前
|
算法 Java
Java面试题:解释垃圾回收中的标记-清除、复制、标记-压缩算法的工作原理
Java面试题:解释垃圾回收中的标记-清除、复制、标记-压缩算法的工作原理
49 1
|
3月前
|
弹性计算 运维 Java
Serverless 应用引擎使用问题之JVM进行垃圾回收时重启,该如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。