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,用来决定是否开启卡表更新的条件判断。开启会增加一次额外判断的开销,但能够避免伪共享问题,两者各有性能损耗,是否打开要根据应用实际运行情况来进行测试权衡。


相关文章
|
3月前
|
监控 算法 Java
Java虚拟机(JVM)的垃圾回收机制深度解析####
本文深入探讨了Java虚拟机(JVM)的垃圾回收机制,旨在揭示其背后的工作原理与优化策略。我们将从垃圾回收的基本概念入手,逐步剖析标记-清除、复制算法、标记-整理等主流垃圾回收算法的原理与实现细节。通过对比不同算法的优缺点及适用场景,为开发者提供优化Java应用性能与内存管理的实践指南。 ####
|
2月前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
76 0
|
2月前
|
算法 网络协议 Java
【JVM】——GC垃圾回收机制(图解通俗易懂)
GC垃圾回收,标识出垃圾(计数机制、可达性分析)内存释放机制(标记清除、复制算法、标记整理、分代回收)
|
2月前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
3月前
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
80 1
|
3月前
|
监控 算法 Java
Java虚拟机垃圾回收机制深度剖析与优化策略####
【10月更文挑战第21天】 本文旨在深入探讨Java虚拟机(JVM)中的垃圾回收机制,揭示其工作原理、常见算法及参数调优技巧。通过案例分析,展示如何根据应用特性调整GC策略,以提升Java应用的性能和稳定性,为开发者提供实战中的优化指南。 ####
61 5
|
3月前
|
存储 算法 安全
JVM常见面试题(四):垃圾回收
堆区域划分,对象什么时候可以被垃圾器回收,如何定位垃圾——引用计数法、可达性分析算法,JVM垃圾回收算法——标记清除算法、标记整理算法、复制算法、分代回收算法;JVM垃圾回收器——串行、并行、CMS垃圾回收器、G1垃圾回收器;强引用、软引用、弱引用、虚引用
|
3月前
|
存储 算法 Java
JVM进阶调优系列(10)敢向stop the world喊卡的G1垃圾回收器 | 有必要讲透
本文详细介绍了G1垃圾回收器的背景、核心原理及其回收过程。G1,即Garbage First,旨在通过将堆内存划分为多个Region来实现低延时的垃圾回收,每个Region可以根据其垃圾回收的价值被优先回收。文章还探讨了G1的Young GC、Mixed GC以及Full GC的具体流程,并列出了G1回收器的核心参数配置,帮助读者更好地理解和优化G1的使用。
|
3月前
|
监控 Java 测试技术
Elasticsearch集群JVM调优垃圾回收器的选择
Elasticsearch集群JVM调优垃圾回收器的选择
110 1
|
4月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
193 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS