您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

JAVA垃圾收集算法总结以及CMS、G1算法详解

时间:2022-02-24 11:32:14  来源:  作者:Java热点

从方法论上讲,程序语言的回收算法主要分为

一、引用计数算法(Reference Counting):给对象添加一个引用计数器,每当一个地方引用它时,数据器加1;当引用失效时,计数器减1;计数器为0的即可被回收。

二、根搜索算法(GC Root Tracing):通过一系列的名为“GC Root”的对象作为起始点,从这些节点开始向下搜索,搜索所有走过的路径称为引用链(Reference ChAIn),当一个对象到GC Root没有任何引用链相连时(用图论来说就是GC Root到这个对象不可达时),证明该对象是可以被回收的。

JAVA采用了根搜索算法,基本原理根据上面解释应该都能理解,基于根搜索方法,又有具体实现算法

一、标记-清除算法(Mark-Sweep)

  最基础的垃圾收集器算法,分为“标记”和“清除”两个阶段,先标记处所需要回收的对象,标记完成后,统一回收掉所有被标记的对象。

  缺点:1)效率问题,标记和清除的效率不高。

2)清除后会产生大量的不连续的内存碎片,可能会导致该程序需要为较大对象分配内存时无法找到足够连续的内存,不得不提前触发垃圾收集动作。

二、复制算法(Copying)

  将内存容量分成大小相等的两块,每次只使用其中一块,当一块用完时,将还存活的对象复制到另一块去,然后把之前使用满的那块空间一次性清理掉,如此反复。

  优点:内存分配的时候不用考虑内存碎片问题,只移动堆顶指针,按顺序分配即可,简单高效。

  缺点:内存空间浪费大,每次只能使用当前的 能够使用 内存空间的一半;当对象存活率较高时,需要有大量的复制操作,效率低。

三、标记-整理算法(Mark-Compact)

  标记整理是在标记-清算上改进得来的,前面说到标记-清算内存碎片的问题,在标记-整理中有解决。同样有标记阶段,标记出所有需要回收的对象,但是不会直接清理,而是将存活的对象向一端移动,在移动过程中清理掉可回收对象。

  优点:解决了之前内存碎片的问题,特别是在存活率高的时候,效率远高于复制算法。

四、分代收集算法(Generational Collection)

  根据内存对象的存活周期不同,将内存划分成几块,java虚拟机中一般将内存划分成新生代和老年代,当新建对象时一般在新生代中分配内存,在新生代垃圾收集器回收几次后仍然存活的对象,将被移动到老年代,或者当大的对象在新生代中无法分配到足够连续的内存空间时也会直接分配到老年代。

  上面四种算法JVM在回收内存时都有采用,大多都是复合运用多种算法一起实现垃圾回收,具体细节每个算法都可以写很多内容,为了不偏题,我们这里只写CMS、G1,其他的有兴趣可以自己查询资料。CMS算法主要是应用在分代收集算法的老年代里,是从JDK8开始采用, 当然默认没有启用,如果在开发或生产环境想采用CMS,可以修改JVM配置-XX:+UseConcMarkSweepGC : 手动指定老年代使用CMS收集器。

下面进入正题

  CMS定义:英文全称Concurrent Mark Sweep,可以翻译为 并发标记清除,是一种以获取最短回收时间为目标的收集器。这是因为CMS收集器工作时,GC工作线程与用户线程可以并发执行,以此来达到降低收集停顿时间的目的。

CMS收集器仅作用于老年代的收集,是基于标记-清除算法的,它的运作过程分为4个步骤:

JAVA垃圾收集算法总结以及CMS、G1算法详解

 

  • 初始标记(CMS initial mark)
  • 并发标记(CMS concurrent mark)
  • 重新标记(CMS remark)
  • 并发清除(CMS concurrent sweep)

其中,初始标记、重新标记这两个步骤仍然需要Stop-the-world。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始阶段稍长一些,但远比并发标记的时间短。

CMS以流水线方式拆分了收集周期,将耗时长的操作单元保持与应用线程并发执行。只将那些必需STW才能执行的操作单元单独拎出来,控制这些单元在恰当的时机运行,并能保证仅需短暂的时间就可以完成。这样,在整个收集周期内,只有 两次短暂的暂停(初始标记和重新标记), 达到了近似并发的目的

CMS收集器优点:并发收集、低停顿。

CMS收集器缺点

  • CMS收集器对CPU资源非常敏感。
  • CMS收集器无法处理浮动垃圾(Floating Garbage),所以如果采用CMS算法,JVM还是在浮动垃圾很多时,自动运行fullgc一次来清除浮动垃圾。
  • CMS收集器是基于标记-清除算法,该算法的缺点都有。

什么情况下CMS比较适合:

(1)响应时间优先,能接受牺牲一定的吞吐量,如果需要高响应时间和高吞吐量,推荐使用G1。后续文章再继续介绍G1。G1也是JDK9默认的垃圾收集器;

(2)硬件配置较高,即CPU和内存资源充足。CMS由于需要与用户线程并发执行,所以可能会竞争CPU资源。同时CMS并发标记阶段,用户线程同时执行时会新建对象,故内存占用会比较高;

(3)堆大小在3G到8G之间,同时存活时间较长的对象比较多

下面再简单讲讲G1算法,

  G1重新定义了堆空间,打破了原有的分代模型,将堆划分为一个个区域。这么做的目的是在进行收集时不必在全堆范围内进行,这是它最显著的特点。区域划分的好处就是带来了停顿时间可预测的收集模型:用户可以指定收集操作在多长时间内完成。即G1提供了接近实时的收集特性。

  G1收集器将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。Region的大小是一致的,数值是在1M到32M字节之间的一个2的幂值数,JVM会尽量划分2048个左右、同等大小的Region。其实这个数字既可以手动调整,G1也会根据堆大小自动进行调整。

G1收集的运作过程大致如下:

  • 初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这阶段需要停顿线程,但耗时很短
  • 并发标记(Concurrent Marking):是从GC Roots开始堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行。
  • 最终标记(Final Marking):是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但是可并行执行
  • 筛选回收(Live Data Counting and Evacuation):首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。这个阶段也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。
  • 下图就是G1堆空间分布
  •  
JAVA垃圾收集算法总结以及CMS、G1算法详解

 

G1特点:

  • 并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-the-world停顿的时间,部分其他收集器原来需要停顿Java线程执行的GC操作,G1收集器仍然可以通过并发的方式让Java程序继续运行。
  • 分代收集
  • 空间整合:与CMS的标记-清除算法不同,G1从整体来看是基于标记-整理算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的。但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC
  • 可预测的停顿:这是G1相对于CMS的一个优势,降低停顿时间是G1和CMS共同的关注点。

总结

随着JVM的发展,ORACLE官方推出的JDK11又有了新算法ZGC,它对内存碎片的整理更加优化,回收暂停时间也更加缩短,具体细节本人还没有深入研究,后面有机会可以写文章专门介绍它。

最后,我把目前主流JDK使用到的JVM垃圾收集器采用的算法做下简单总结,方便大家对比参考,

  1. 新生代垃圾收集器
  • Serial-复制算法:Serial收集器是新生代单线程收集器,优点是简单高效,算是最基本、发展历史最悠久的收集器。它在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集完成。
  • ParNew收集器-复制算法:ParNew收集器是新生代并行收集器,其实就是Serial收集器的多线程版本。
  • Parallel Scavenge(并行回收)-复制算法:
  • Parallel Scavenge收集器是新生代并行收集器,追求高吞吐量,高效利用 CPU。该收集器的目标是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即 吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可用高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。

  2. 老年代垃圾收集器

  • Serial Old-标记整理算法:Serial Old是Serial收集器的老年代版本,它同样是一个单线程(串行)收集器,使用标记整理算法。这个收集器的主要意义也是在于给Client模式下的虚拟机使用
  • Parallel Old-标记整理算法:Parallel Old 是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。
  • CMS:标记清除算法
  • G1:标记整理算法


Tags:JAVA垃圾   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Java垃圾回收器的工作原理及监视不再使用对象的机制
Java作为一门面向对象的编程语言,具有自动内存管理的特性。这意味着开发人员无需手动分配和释放内存,而是由Java虚拟机的垃圾回收器负责管理。垃圾回收器通过监视程序中不再使...【详细内容】
2023-12-27  Search: JAVA垃圾  点击:(137)  评论:(0)  加入收藏
Java垃圾回收器对循环引用对象的处理机制
循环引用的定义与问题循环引用是指两个或多个对象之间形成了相互引用的关系,形成了一个环状结构。例如,对象A引用了对象B,而对象B又引用了对象A,它们之间形成了一个循环引用。这...【详细内容】
2023-12-25  Search: JAVA垃圾  点击:(122)  评论:(0)  加入收藏
JAVA垃圾收集算法总结以及CMS、G1算法详解
从方法论上讲,程序语言的回收算法主要分为一、引用计数算法(Reference Counting):给对象添加一个引用计数器,每当一个地方引用它时,数据器加1;当引用失效时,计数器减1;计数器为0的即...【详细内容】
2022-02-24  Search: JAVA垃圾  点击:(349)  评论:(0)  加入收藏
被说烂了的Java垃圾回收算法,我带来了最“清新脱俗”的详细图解
一、概况理解Java虚拟机垃圾回收机制的底层原理,是系统调优与线上问题排查的基础,也是一个高级Java程序员的基本功,本文就针对Java垃圾回收这一主题做一些整理与记录。Java垃...【详细内容】
2020-07-15  Search: JAVA垃圾  点击:(258)  评论:(0)  加入收藏
一篇文章彻底了解Java垃圾收集(GC)机制
垃圾收集(Garbage Collection ,GC),是一个长久以来就被思考的问题,当考虑GC的时候,我们必须思考3件事情: 哪些内存需要回收? 什么时候回收? 如何回收?那么在Java中,我们要怎么来考虑GC...【详细内容】
2019-11-01  Search: JAVA垃圾  点击:(597)  评论:(0)  加入收藏
▌简易百科推荐
Java 8 内存管理原理解析及内存故障排查实践
本文介绍Java8虚拟机的内存区域划分、内存垃圾回收工作原理解析、虚拟机内存分配配置,以及各垃圾收集器优缺点及场景应用、实践内存故障场景排查诊断,方便读者面临内存故障时...【详细内容】
2024-03-20  vivo互联网技术    Tags:Java 8   点击:(17)  评论:(0)  加入收藏
如何编写高性能的Java代码
作者 | 波哥审校 | 重楼在当今软件开发领域,编写高性能的Java代码是至关重要的。Java作为一种流行的编程语言,拥有强大的生态系统和丰富的工具链,但是要写出性能优异的Java代码...【详细内容】
2024-03-20    51CTO  Tags:Java代码   点击:(25)  评论:(0)  加入收藏
在Java应用程序中释放峰值性能:配置文件引导优化(PGO)概述
译者 | 李睿审校 | 重楼在Java开发领域,优化应用程序的性能是开发人员的持续追求。配置文件引导优化(Profile-Guided Optimization,PGO)是一种功能强大的技术,能够显著地提高Ja...【详细内容】
2024-03-18    51CTO  Tags:Java   点击:(29)  评论:(0)  加入收藏
Java生产环境下性能监控与调优详解
堆是 JVM 内存中最大的一块内存空间,该内存被所有线程共享,几乎所有对象和数组都被分配到了堆内存中。堆被划分为新生代和老年代,新生代又被进一步划分为 Eden 和 Survivor 区,...【详细内容】
2024-02-04  大雷家吃饭    Tags:Java   点击:(60)  评论:(0)  加入收藏
在项目中如何避免和解决Java内存泄漏问题
在Java中,内存泄漏通常指的是程序中存在一些不再使用的对象或数据结构仍然保持对内存的引用,从而导致这些对象无法被垃圾回收器回收,最终导致内存占用不断增加,进而影响程序的性...【详细内容】
2024-02-01  编程技术汇  今日头条  Tags:Java   点击:(75)  评论:(0)  加入收藏
Java中的缓存技术及其使用场景
Java中的缓存技术是一种优化手段,用于提高应用程序的性能和响应速度。缓存技术通过将计算结果或者经常访问的数据存储在快速访问的存储介质中,以便下次需要时可以更快地获取。...【详细内容】
2024-01-30  编程技术汇    Tags:Java   点击:(75)  评论:(0)  加入收藏
JDK17 与 JDK11 特性差异浅谈
从 JDK11 到 JDK17 ,Java 的发展经历了一系列重要的里程碑。其中最重要的是 JDK17 的发布,这是一个长期支持(LTS)版本,它将获得长期的更新和支持,有助于保持程序的稳定性和可靠性...【详细内容】
2024-01-26  政采云技术  51CTO  Tags:JDK17   点击:(95)  评论:(0)  加入收藏
Java并发编程高阶技术
随着计算机硬件的发展,多核处理器的普及和内存容量的增加,利用多线程实现异步并发成为提升程序性能的重要途径。在Java中,多线程的使用能够更好地发挥硬件资源,提高程序的响应...【详细内容】
2024-01-19  大雷家吃饭    Tags:Java   点击:(111)  评论:(0)  加入收藏
这篇文章彻底让你了解Java与RPA
前段时间更新系统的时候,发现多了一个名为Power Automate的应用,打开了解后发现是一个自动化应用,根据其描述,可以自动执行所有日常任务,说的还是比较夸张,简单用了下,对于office、...【详细内容】
2024-01-17  Java技术指北  微信公众号  Tags:Java   点击:(102)  评论:(0)  加入收藏
Java 在 2023 年仍然流行的 25 个原因
译者 | 刘汪洋审校 | 重楼学习 Java 的过程中,我意识到在 90 年代末 OOP 正值鼎盛时期,Java 作为能够真正实现这些概念的语言显得尤为突出(尽管我此前学过 C++,但相比 Java 影响...【详细内容】
2024-01-10  刘汪洋  51CTO  Tags:Java   点击:(81)  评论:(0)  加入收藏
站内最新
站内热门
站内头条