• JVM -- 垃圾回收器7种(四)


    阅读前可参考

    https://blog.csdn.net/MinggeQingchun/article/details/126947384

    https://blog.csdn.net/MinggeQingchun/article/details/127066302

    https://blog.csdn.net/MinggeQingchun/article/details/127089533

    JVM官方调优指南

    https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/

    一、评估GC的性能指标

    1、吞吐量(throughput)

    吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值

    即吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)

    运行用户代码的时间占总时间的比例 (总运行时间::程序运行时间+内存回收时间)

    垃圾收集开销:吞吐量的补数,垃圾收集器所用时间与总时间的比例

    高吞吐量的情况下,应用程序能容忍较高的暂停时间,因此高吞吐量的应用程序由更长的时间基准,快速响应是不必考虑的

    2、暂停时间(响应时间优先)

    暂停时间是指一个时间段内应用程序线程暂停,让GC线程执行的状态

    执行垃圾收集时,程序的工作线程被暂停的时间

    收集频率:相对于应用程序的执行,收集操作发生的频率

    3、内存占用:Java堆区所占的内存大小

    快速:一个对象从诞生到被回收所经历的时间

    这三者共同构成一个"不可能三角",三者总体的表现随着技术的进步越来越好,一款优秀的收集器通常最多同时满足其中两项

    现在标准:在最大吞吐量优先的情况下,降低暂停时间吞吐量(throughput)

    二、垃圾回收器分类

    1、按线程数分

    可分为串行垃圾回收器和并行垃圾回收器

    2、按工作模式分

    可分为并发式垃圾回收器和独占式垃圾回收器

    3、按碎片处理方式分

    可分为压缩式垃圾回收器和非压缩式垃圾回收器

    4、按工作的内存空间分

    可分为年起代垃圾收集器和老年代垃圾收集器

    三、 七种经典垃圾回收器

    JVM的垃圾回收器大体上的分类主要包括四种:串行、并行、并发(CMS)和G1

    串行垃圾回收器(Serial):它为单线程环境设计并且只使用一个线程进行垃圾回收,会暂停所有的用户线程。所以不适合服务器环境

    并行垃圾回收器(Parallel):多个垃圾回收线程并行工作,此时用户线程是暂停的,适用于科学计算/大数据处理等弱交互场景

    并发垃圾回收器(CMS):用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程。互联网公司多用它,适用于对响应时间有要求的场景

    G1垃圾回收器:G1垃圾回收器将堆内存分割成不同的区域然后并发的对其进行垃圾回收

    ---------------------------------------------------------------------------------------------------------------------------------

    新生代回收器:Serial、ParNew、Parallel Scavenge

    老年代回收器:CMS、Serial Old、Parallel Old

    全堆:G1

    新生代和老年代区域的回收器之间可以搭配使用

    (一)新生代回收器:Serial、ParNew、Parallel Scavenge

    1、Serial 垃圾回收器

    Serial收集器是最基本的、发展历史最悠久的收集器。俗称为:串行回收器,采用复制算法进行垃圾回收

    特点

    串行回收器是指使用单线程进行垃圾回收的回收器。每次回收时,串行回收器只有一个工作线程。

    对于并行能力较弱的单CPU计算机来说,串行回收器的专注性和独占性往往有更好的性能表现。

    它存在Stop The World问题,即垃圾回收时,要停止程序的运行。

    使用-XX:+UseSerialGC参数可以设置新生代使用这个串行回收器

    2、ParNew 垃圾回收器

    ParNew其实就是Serial的多线程版本,除了使用多线程之外,其余参数和Serial一模一样。俗称:并行垃圾回收器,采用复制算法进行垃圾回收

    特点

    ParNew默认开启的线程数与CPU数量相同,在CPU核数很多的机器上,可以通过参数-XX:ParallelGCThreads来设置线程数

    它是目前新生代首选的垃圾回收器,因为除了ParNew之外,它是唯一一个能与老年代CMS配合工作的。

    它同样存在Stop The World问题

    使用-XX:+UseParNewGC参数可以设置新生代使用这个并行回收器

    3、ParallelGC 回收器

    ParallelGC使用复制算法回收垃圾,也是多线程的

    特点

    ParallelGC是非常关注系统的吞吐量,吞吐量=代码运行时间/(代码运行时间+垃圾收集时间)

    -XX:MaxGCPauseMillis:设置最大垃圾收集停顿时间,可用把虚拟机在GC停顿的时间控制在MaxGCPauseMillis范围内,如果希望减少GC停顿时间可以将MaxGCPauseMillis设置的很小,但是会导致GC频繁,从而增加了GC的总时间降低吞吐量。所以需要根据实际情况设置该值。

    -Xx:GCTimeRatio:设置吞吐量大小,它是一个0到100之间的整数,默认情况下他的取值是99,那么系统将花费不超过1/(1+n)的时间用于垃圾回收,也就是1/(1+99)=1%的时间。

    另外还可以指定-XX:+UseAdaptiveSizePolicy打开自适应模式,在这种模式下,新生代的大小、eden、from/to的比例,以及晋升老年代的对象年龄参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。

    使用-XX:+UseParallelGC参数可以设置新生代使用这个并行回收器

    (二)老年代回收器:CMS、Serial Old、Parallel Old

    1、SerialOld 垃圾回收器

    SerialOld是Serial回收器的老年代回收器版本,它同样是一个单线程回收器。

    用途

    • 一个是在JDK1.5及之前的版本中与Parallel Scavenge收集器搭配使用,

    • 另一个就是作为CMS收集器的后备预案,如果CMS出现Concurrent Mode Failure,则SerialOld将作为后备收集器。

    使用算法标记 - 整理算法

    2、ParallelOldGC 回收器

    老年代ParallelOldGC回收器也是一种多线程的回收器,和新生代的ParallelGC回收器一样,也是一种关注吞吐量的回收器,他使用了标记- 整理/压缩算法进行实现。

    -XX:+UseParallelOldGc进行设置老年代使用该回收器

    -XX:+ParallelGCThreads也可以设置垃圾收集时的线程数量。

    3、CMS 回收器

    CMS全称为:Concurrent Mark Sweep意为并发标记清除,他使用的是标记清除算法。主要关注系统停顿时间。

    使用-XX:+UseConcMarkSweepGC进行设置老年代使用该回收器。

    使用-XX:ConcGCThreads设置并发线程数量。

    特点

    CMS并不是独占的回收器,也就说CMS回收的过程中,应用程序仍然在不停的工作,又会有新的垃圾不断的产生,所以在使用CMS的过程中应该确保应用程序的内存足够可用。

    CMS不会等到应用程序饱和的时候才去回收垃圾,而是在某一阀值的时候开始回收,回收阀值可用指定的参数进行配置:-XX:CMSInitiatingoccupancyFraction来指定,默认为68,也就是说当老年代的空间使用率达到68%的时候,会执行CMS回收

    如果内存使用率增长的很快,在CMS执行的过程中,已经出现了内存不足的情况,此时CMS回收就会失败,虚拟机将启动老年代串行回收器;SerialOldGC进行垃圾回收,这会导致应用程序中断,直到垃圾回收完成后才会正常工作。

    这个过程GC的停顿时间可能较长,所以-XX:CMSInitiatingoccupancyFraction的设置要根据实际的情况。

    标记清除算法有个缺点就是存在内存碎片的问题,那么CMS有个参数设置-XX:+UseCMSCompactAtFullCollecion可以使CMS回收完成之后进行一次碎片整理

    -XX:CMSFullGCsBeforeCompaction参数可以设置进行多少次CMS回收之后,对内存进行一次压缩

    (三)全堆:G1

    G1(Garbage-First)是一款面向服务器的垃圾收集器,支持新生代和老年代空间的垃圾收集,主要针对配备多核处理器及大容量内存的机器,G1最主要的设计目标是:实现可预期及可配置的STW停顿时间

    1、G1分区


    G1将Java堆划分为多个大小相等的独立区域(Region),每一个小方格代表一个Region,JVM最多可以有2048个Region

    一般Region大小等于堆大小除以2048,比如堆大小为4096M,则Region大小为2M,当然也可以用参数-XX:G1HeapRegionSize手动指定Region大小,但是推荐默认的计算方式

    1、Region

    为实现大内存空间的低停顿时间的回收,将划分为多个大小相等的Region。每个小堆区都可能是 Eden区,Survivor区或者Old区,但是在同一时刻只能属于某个代

    在逻辑上, 所有的Eden区和Survivor区合起来就是新生代,所有的Old区合起来就是老年代,且新生代和老年代各自的内存Region区域由G1自动控制,不断变动

    2、巨型对象(Humongous区)

    G1有专门分配大对象的Region叫Humongous区,而不是让大对象直接进入老年代的Region中。在G1中,大对象的判定规则就是一个大对象超过了一个Region大小的50%,比如每个Region是2M,只要一个对象超过了1M,就会被放入Humongous中,而且一个大对象如果太大,可能会横跨多个Region来存放。

    作用:Humongous区专门存放短期巨型对象,不用直接进老年代,可以节约老年代的空间,避免因为老年代空间不够的GC开销

    Full GC的时候除了收集年轻代和老年代之外,也会将Humongous区一并回收

    2、G1 垃圾回收器工作流程

    1、初始标记(Initial Marking)

    这阶段仅仅只是标记GC Roots能直接关联到的对象并修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确的可用的Region中创建新对象,这阶段需要停顿线程,但是耗时很短。而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿

    2、并发标记(Concurrent Marking)

    从GC Roots开始对堆的对象进行可达性分析,递归扫描整个堆里的对象图,找出存活的对象,这阶段耗时较长,但是可以与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。

    3、最终标记(Final Marking)

    对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的 SATB 记录

    4、筛选回收(Live Data Counting and Evacuation)

    负责更新 Region 的统计数据,对各个 Region 的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划。可以自由选择多个Region来构成会收集,然后把回收的那一部分Region中的存活对象==复制==到空的Region中,在对那些Region进行清空

    除了并发标记外,其余过程都要 STW

    stop-the-world(STW)

    stop-the-world会在任何一种GC算法中发生。stop-the-world 意味着JVM因为需要执行GC而停止应用程序的执行。

    当stop-the-world 发生时,除GC所需的线程外,所有的线程都进入等待状态,直到GC任务完成。GC优化很多时候就是减少stop-the-world 的发生

    3、G1垃圾收集分类

    1、YoungGC

    YoungGC并不是说现有的Eden区放满了就会马上触发,G1会计算下现在Eden区回收大概要多久时间,如果回收时间远远小于参数 -XX:MaxGCPauseMills 设定的值,那么增加年轻代的region,继续给新对象存放,不会马上做Young GC,直到下一次Eden区放满,G1计算回收时间接近参数 -XX:MaxGCPauseMills 设定的值,那么就会触发Young GC

    G1垃圾收集器刚开始年轻代只占堆内存5%,会随着每次计算回收时间而增加,最多不超过60%

    2、MixedGC【混合收集】

    不是FullGC,老年代的堆占有率达到参数-XX:InitiatingHeapOccupancyPercent设定的值则触发,回收所有的年轻代和部分老年代(根据筛选回收阶段计算优先级后排序)以及大对象区,正常情况G1的垃圾收集是先做MixedGC,主要使用复制算法,需要把各个region中存活的对象拷贝到别的region里去,拷贝过程中如果发现没有足够的空region能够承载拷贝对象就会触发一次Full GC

    3、Full GC

    停止系统程序,然后采用单线程进行标记、清理、压缩整理以空闲出来一批Region来供下一次MixedGC使用,这个过程是非常耗时的。(Shenandoah优化成多线程收集了)

    (1)SerialGC

    新生代内存不足发生的垃圾收集 - minor gc

    老年代内存不足发生的垃圾收集 - full gc

    (2)ParallelGC

    新生代内存不足发生的垃圾收集 - minor gc

    老年代内存不足发生的垃圾收集 - full gc

    (4)CMS

    新生代内存不足发生的垃圾收集 - minor gc

    老年代内存不足

    (5)G1 新生代内存不足发生的垃圾收集 - minor gc

    老年代内存不足

    4、G1和CMS的区别

    1、G1从整体上来看是 标记-整理 算法,但从局部(两个Region之间)是复制算法。而CMS是 标记-清除算法。所以说,G1不会产生内存碎片,而CMS会产生内存碎片

    2、CMS使用了 写后屏障来维护卡表,而G1不仅使用了写后屏障来维护卡表,还用了 写前屏障来跟踪并发时的指针变化情况(为了实现原始快照)

    3、CMS对Java堆内存使用的是传统的 新生代和老年代划分方法,而G1使用的全新的划分方法

    4、CMS收集器只收集老年代,可以配合新生代的Serial和ParNew收集器一起使用。G1收集器收集范围是老年代和新生代。不需要结合其他收集器使用

    5、CMS使用 增量更新解决并发标记下出现的错误标记问题,而G1使用原始快照解决

    5、使用G1场景

    1、50%以上的堆被存活对象占用

    使用G1,就不用特意预留出很大的老年代空间,G1会根据对象存活状态,动态分配每种不同代对象需要占用的空间。

    2、对象分配和晋升的速度变化非常大

    前提还是大内存机器才使用G1,大内存的主机如果对象分配和晋升的速度变化非常快的话,G1的这种内存设计可以很快的划分出对应所需的区域【区域占比动态增长,不像CMS等垃圾收集器要划分固定的空间来区分年轻代和老年代】,但因为G1算法比较复杂,在小内存机器里面性能不如CMS等主流垃圾收集器。

    3、垃圾回收时间特别长,超过1秒

    G1有一大好处就是可以设置我们每次想要回收的停顿时间【-XX:MaxGCPauseMillis】,可以有效提升用户体验。

    4、8GB以上的堆内存(建议值)

    G1适合8G以上内存的机器使用【结构设计,2048个Region,内存太小的话每个Region也很小,很容易就超过Region的一半被识别为超大对象,这样Humongous区东西会很多,反而不能很好的进行GC收集】

    5、停顿时间是500ms以内

    和第三点类似,G1分段收集【每次不一定全部回收完所有的垃圾对象】,并且可以手动设置我们每次GC想要STW的时间

  • 相关阅读:
    计算机毕业设计 基于SpringBoot笔记记录分享网站的设计与实现 Javaweb项目 Java实战项目 前后端分离 文档报告 代码讲解 安装调试
    代码随想录Day42 | 01背包问题| 416. 分割等和子集
    Linux学习
    2022 RAD Studio Delphi 11.2 新版本发布并引入了新工具和质量改进升级
    艾美捷ProSci丨ProSci TM4SF1 抗体解决方案
    LeetCode算法题解(回溯)|39. 组合总和、40. 组合总和 II、131. 分割回文串
    研究mysql日志的使用
    【C++STL基础入门】排序和遍历容器
    LeetCode链表问题——19.删除链表的倒数第N个节点(一题一文学会链表)
    mysql主主复制
  • 原文地址:https://blog.csdn.net/MinggeQingchun/article/details/127093159