JVM篇-垃圾回收器(最核心篇)

本文详细介绍了JVM中的垃圾回收器,包括GC的分类、性能指标和各种类型的垃圾收集器如Serial、ParNew、Parallel Scavenge、CMS和G1。重点讨论了这些收集器的特点、工作原理、优势与劣势,以及如何选择合适的垃圾回收器以优化系统的性能和响应时间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

GC分类与性能指标

GC分类

  • 垃圾收集器没有在规范中进行过多的规定,可以有不同的厂商、不同的版本的JVM来实现
  • 由于JDK的版本处于高速迭代的过程,因此Java发展至今已经衍生了众多GC版本
  • 从不同角度分析垃圾收集器,可以将GC分为不同的类型
    • 按照线程分为:串行垃圾回收器(默认在JDK Client模式)并行垃圾回收器
    • 按照工作模式分:可以分为并发式垃圾回收器独占式垃圾回收器
    • 按照碎片处理方式分:分为压缩式垃圾回收器(再分配对象空间使用:指针碰撞)非垃圾回收器(再分配对象空间使用:空闲列表)
    • 工作内存空间分:年轻代垃圾回收器老年代垃圾回收器

性能指标

  • 吞吐量:运行用户代码的时间总运行实间的比例
  • 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间
  • 内存占用Java堆区所占的内存大小
  • 垃圾收集开销:吞吐量的补数,垃圾收集所用时间与总运行时间的比例
  • 收集频率::相对于应用程序的执行,收集操作发生的频率
  • 快速:一个对象从诞生到被回收所经历的时间。
    不可能三角吞吐量暂停时间(STW)、内存占用只能满足两点

吞吐量(ThroughPut)

  • 吞吐量是CPU用于运行用户代码的时间与CPU总耗时的比值,
    • 吞吐量计算=运行用户代码时间/(运行用户代码时间+垃圾回收时间
  • 这种情况下,应用程序能容忍较高的暂停时间,因此高吞吐量的引用程序有更长的时间基准,快速响应式不必考虑
  • 吞吐量优先,意味着在单位时间内,STW的时间次数少

在这里插入图片描述

暂停时间(Pause Time)

  • 暂停时间是指一段时间内引用程序线程暂停,让GC线程执行的状态
    • GC期间100毫秒的暂停时间意味着在这100毫秒期间内没有引用程序线城市活动的
  • 暂停时间优先,意味着竟可能让赞提STW的时间最短
    在这里插入图片描述

现在标准

在最大吞吐量优先的情况下,降低停顿时间

不同的垃圾回收器概述

Java常见的垃圾收集器有哪些?

经典垃圾收集器

  • 串行回收器:Serial、Serial Old
  • 并行回收器:ParNew、Parallel Scavenge、Parallel Old
  • 并发回收器:CMS、G1

垃圾收集器分代之间的关系

在这里插入图片描述

  • 新生代收集器:Serial ParNew Parallel Scavege
  • 老年代收集器: Serial Old Parallel Old CMS
  • 整堆收集器:G1

垃圾收集器的组合关系(JDK14)

在这里插入图片描述

  • 为什么需要这么多垃圾收集器,一个不够嘛?因为Java应用场景多。
  • 我要选择的是针对具体应用最适合的收集器

如何查看默认的垃圾收集器

	-XX:+PrintCommandLineFlags:查看命令行线管参数 包括使用的垃圾收集器
	jinfo -flag 相关垃圾回收器参数 进程id

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Serial回收器:串行回收

  • Serial 收集器作为HotSpot中Client模式下的默认新生代垃圾收集器
  • Serial收集采用复制算法串行回收STW机制的方式执行内存回收。
  • 除了收集年轻代之外,Serial收集器还提供了用于收集老年代的Serial Old收集器,Serial Old收集器同样采用了了串行收集 和STW机制,只不过内存回收算法使用的是标记压缩算法
    • Client 模式下默认使用老年代的垃圾回收器
    • Serial Old在Server模式下主要有两个用途
      • 与新生代的Parallel Scavenge 配合使用
      • 作为老年代CMS收集器的后背垃圾收集方案(CMS2020.3已经删除)

执行过程

在这里插入图片描述只会使用一个CPU或者一条收集线程
必须STW,垃圾回收完了继续执行

优势:

单线程:简单高效

使用

-XX:PrintCommandLineFlags# 查看使用的垃圾收集器
-XX:+UseSerialGC#表示新生代使用SerialGC 老年代使用SerialOldGC

小结

  • 这种垃圾收集器是串行的一半就在单核的CPU才可以使用
  • 注重加护强的应用而言,这种垃圾收集器不适用,如JavaWeb不使用

ParNew回收器:并行回收

  • 并行回收
  • 采用复制算法
  • 采用STW机制
  • JVM在Server模式使用新生代使用和这个回收器(不是针对最新的,最新的不用)
  • 老年代还是使用SerialOldGC
    在这里插入图片描述

并行回收

  • ** ParNew只是运行多CPU的环境下,单CPU环境Serial回收效率更高**

使用

-XX:+UseParNewGC #手动指定收集器(只表示年轻代)
-XX:ParallelGCThreads #限制线程数量(默认开启和CPU数据相同的线程数) 

Parallel吞吐量优先

Parallel Scavenge收集器也采用与ParNew采用复制算法、并行回收与Stop The World

有了ParNew的并行收集器为什么还要这个收集器?

  • 与ParNew收集器不同,Parallel Scavenge 收集器是为了达到一个可控制的吞吐量,也被成为吞吐量优先的垃圾收集器
  • 自适应调节策略也是Parallel Scavenge与ParNew一个重要区别。

吞吐量优先

  • 高吞吐量可以高效的利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务,因此在服务器环境中使用
    • 如:执行批量处理、订单处理、工资支付、科学计算
  • Parallel收集器在JDK1.6时提供了用于老年代收集Parallel Old收集器,用来替代老年代的Serial Old收集器
  • Parallel Old收集器采用了标记-压缩算法,但同样也是基于并行回收STW机制

参数设置(JDK8默认使用这两个)

-XX:+UseParallelGC:表明新生代使用Parallel GC 
-XX:+UseParallelOldGC : 表明老年代使用ParallelOldGC
 说明:二者可以相互激活(参数只要使用一个即可)
 -XX:ParallelGCThreads 设置年轻代并行收集器的线程数
 //默认CPU数量小于8个,这个垃圾回收器线程默认的值等于CPU的数量
 //大于八个 ParallelGCThreads的计算公式就是==>3+(5*Cpu个数)/8

-XX:MaxGCPauseMillis 设置垃圾收集器的最大停顿时间(STW的时间)单位毫秒(慎用)
-XX:GCTimeRatio垃圾收集时间占用总时间的比例(=1/(N+1))用于很亮吞吐量的大小

  • 取值范围(0,100)默认99,也就是垃圾回收时间不超过1%
  • 与前一个参数MaxGCPauseMillis参数是矛盾的,暂停时间越长Radio参数越容易超过设定比例。

-XX:+UseAdaptiveSizePolicy默认开启使用) 设置Parallel Scavenge收集器具有自适应调节策略

  • 这种模式下,年轻代的大小 Eden Survivor的比例,晋升老难带的年龄平均参数会被自动调整,已达到堆的大小,吞吐量和停顿时间的平衡点
  • 手动调优比较困难的场合,可以直接只用这个自适应的方式,让虚拟机自动优化。

CMS回收器:低延迟

  • 真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作
  • CMS(Concurrent-Mark-Sweep)收集器,关注点是尽可能减少用户线程的停顿时间低延迟
    • 目前很大一部分的Java意义几种在与互联网栈或者B/S系统的服务端上,这列意义尤其注重服务的响应速度,希望系统停顿时间最短
  • CMS(针对老年代的手机)的垃圾收集算法财通标记-清除算法,也会STW
  • 不能与Parallel Scavenge同时使用,只能与ParNew配合使用

工作原理

在这里插入图片描述

  • 初始标记(Initial-Mark)阶段:仅仅只是标记出GC Roots直接关联到的对象,STW十分短,因此速度很快
  • 并发标记(Concurrent-Mark)阶段:从GC Roots的直接对象开始遍历整个遍历真个对象图的过程,这个过程耗时长,但是不会暂停用户线程垃圾收集线程与之并发运行
  • 重新标记(Remark)阶段:修正并发标记期间,因用户程序继续运作导致标记产生变动的一部分对象的标记记录。(比初始标记阶段时间长一点,但是比并发标记阶段时间短很多
  • 并发清除(Concurrent-Sweep)阶段:此阶段清理删除出掉标记阶段判断的已经死亡的对象释放内存空间,由于不需要移动存活的对象,这个阶段因此也是可以与用户线程同时并发的。

注意事项

  • CMS回收过程中,应该确保由于程序用户线程有足够的内存可用。
    • 因此为了确保不出现(Concurent Mode Failure),当堆内存使用率达到使用率的阈值时,便开始回收。
    • 如果因出现Concurrent Mode Failure导致CMS失败,虚拟机会启用这个Serial Old进行重新回收,如此停顿时间也会长
  • CMS收集器的垃圾收集算法采用的标记-清除算法会导致产生内存碎片
  • 那么Mark Sweep会导致内存碎片,为什么不把算法替换成Mark Compact呢?
    • 因为要减少STW达到低延迟,而标记-压缩算法进行整理,由于用户线程与垃圾回收线程并发执行,整理会导致修改对象的地址,造成严重的后果

优劣分析

优点

  • 并发收集
  • 低延迟

劣势

  • 会产生内存碎片:并发清除后,用户线程可用空间不足,导致无法分配大的对象,继而触发Full GC
  • CMS收集器会对CPU资源非常敏感:虽不会导致用户停顿,但是因为占用一部分线程而导致应用程序变慢,总吞吐量降低
  • CMS收集器无法处理浮动垃圾:可能出现CMF失败而导致另一次Full GC的产生,在并发标记阶段由于程序的工作线程和垃圾收集线程是同时运行或者交叉运行,那么在并发标记阶段如果产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终导致新产生的垃圾对象没有被及时回收。

CMS收集器可以设置的参数

  • -XX:+UseConcMarkSweep::设置后会自动打开ParNewGC(年轻代垃圾收集器),老年代(CMS+SerialOld)
  • -XX:CMSInitiatingOccupanyFraction设置堆内存使用率的阈值,一旦达到阈值,进进行回收
    • JDK5以及以前是68,JDK6以上是92%
    • 若内存增长慢,就设置稍大点的值,可以有效降低Full GC的次数
  • -XX:+UseCMSCompactAtFullCollection用于指定执行完FullGC后对内存空间进行压缩整理,以此来避免内存碎片的产生,不过由于内存压缩整理过程无法并发执行,所以带来问题就是停顿时间更长从而影响低延迟
  • -XX:CMSFullGCsBeforeCompaction设置在执行多少次Full GC后对内存空间进行压缩。
  • -XX:ParallelCMSThreads设置CMS的线程数量
    • 默认启动的线程数是(CPU个数(ParallelGCThreads)+3)/4

小结

  • 最小内存并行开销选Serial GC
  • 最大化应用程勋吞吐量选Parallel GC
  • 最低延迟选择CMS

G1回收器:区域化分代式

既然我们有了强大的GC,为什么还要发布Garbage First(G1)?

- 业务越来越庞大、复杂、用户量增长。
- 不断扩大的内存和不断增加的处理器数量
- G1的设定目标是:**延迟可控下,尽可能提高吞吐量。**

为什么要叫Garbage First?

  • 因为G1是一个并行回收器,它把堆内存分割为很多**不相关的区域(Region)**物理上不连续,使用不同的Region,表示伊甸园区,幸存者0区,幸存者1区,老年代。
  • G1 GC有计划避免整个Java堆中进行全区域的垃圾收集,G1跟踪各个Region里面的垃圾堆积的价值代销,在后台优先维护一个列表,每次根据允许的收集时间,优先回收价值最大的Region
  • 这种方式侧重点在于回收垃圾最大量的区间(Region),因此就给G1取了一个名字:垃圾优先(Garbage Frist)

G1介绍

  • 主要是针对配备多核CPU及大容量内存的机器
  • JDK7版本正式启动,溢出了Experimental的标识,JDK9以后设置为默认回收器,取代了CMS,又称为全功能的垃圾收集器
  • 与此同时CMS已经在JDK9被废弃
  • JDK8需要通过-XX:+UseGC来启用

G1回收器的优势

回收模型

在这里插入图片描述

优势

  • 并行与并发
    • 并行性:G1在回收期间,可以有多个GC线程同时工作,有效的利用多核计算能力,减少用户线程STW
    • 并发性:G1拥有与应用程序的交替执行的能力,部分工作可以和应用程序同时执行,因此一般来说,不会在整个回收阶段发生完全阻塞引用程序的情况
  • 分代收集
    • 分代上看:G1属于分代型垃圾收集器
    • 将堆空间分为若干区域(Region),这些区域包含了逻辑上的年轻代和老年代。
    • 和之前各类回收器不同,它同时兼顾年轻代和老年代,对比其他回收器,或者工作在年轻代,或工作在老年代。
  • 空间整合
    • CMS存在内存碎片,若干次GC后进行一次碎片整理
    • G1将内存划分一个个Region,内存的回收是以Region作为基本单位
      • Region之间是复制算法
      • 整体是标记-压缩算法
      • 这两种算法可以避免内存碎片,这种特性有利于程序长时间运行,分配大对象不会因为无法找到连续内存空间而提前触发下一次GC,Java堆空间大的时候,G1优势更明显
  • 可预测的停顿时间模型 (软实时Soft Real Time)
    • 由于分区原因,G1可以只选取部分区域进行内存回收。
    • G1追踪各个Region里面的垃圾堆积的价值代销,在后台维护一个优先列表,每次根据循序收集时间,优先回收价值最大的Region
    • 相比CMS GC ,G1未必能做到CMS最好情况下的低延迟,但是比CMS最差情况(既使用Serial Old单线程STW回收)会好。

不足

  • 相比CMS,G1还不具备全方位、压倒性优势,比如用户程序运行过程中,无论是为了垃圾收集产生内存占用,还是程序额外执行负载都比CMS要高
  • 小内存应用CMS大概率会优于G1,而G1在最大内存应用上发挥其优势,平衡点在6-8GB

G1回收参数设置

  • -XX:+UseG1GC 设置使用G1垃圾收集器执行内存回收任务
  • -XX:G1HeapRegionSize 设置么个Region的大小,值是2的幂,范围是1MB-32MB
  • -XX:MaxGCPauseMillis 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证能达到)默认 200ms
  • -XX:ParallelGGCThread 设置STW工作线程数的值,最多为8
  • -XX:ConcGCThreads 设置并发标记的线程数,将n设置为并行垃圾回收线程数的1/4左右
  • -XX:InitiatingHeapOccupancyPercent 设置触发GC周期的Java堆占用率阀值,超过这个值触发GC,默认45

G1回收器操作步骤

  1. 开启G1垃圾收集器
  2. 设置堆最大内存
  3. 设置最大停顿时间
  • G1提供三种垃圾回收模式
    • YoungGC
    • Mixed GC
    • Full GC

G1回收器适用场景

  • 面向服务端应用,针对具有大内存、多处理器的机器
  • 最主要应用是需要低GC延迟,并且具有大堆的应用程序提供解决方案
    • 堆大小约6GB往上,可预测的暂停时间可以抵御0.5s
  • 用来替换JDK1.5中的CMS收集器
    • 超过50%的Java堆被活动数据占用
    • 对象分配频率霍年代提升效率变换很大
    • GC停顿时间过长

分区(Region)

-XX:G1HeapRegionSize可以设定Region大小
- 所有的Region大小相同,且在JVM生命周期内不会改变
- 物理上不是连续的
在这里插入图片描述

设置Humongous区的原因

  • 对于堆中的大对象会被分配到老年代,但是如果他是一个短期存在的大对象就会对垃圾收集器造成负面影响,为了解决这个问题,G1划分一个Humongous区,它用来专门存储大对象

    • 如果一个H区装不下一个大对象,那么G1会寻找连续的H区来存储
    • 为了能找到连续的H区,优势会启动Full GC
  • 指针碰撞
    在这里插入图片描述

G1回收器回收过程

G1的垃圾回收过程主要包括如下三个环节
在这里插入图片描述

  • 年轻代GC(YoungGC)
    • 当年轻代Eden区用尽时开始年轻代回收
    • 当堆内存占用达到45%,老年代开始并发标记过程
    • 标记完成马上进入混合回收过程,G1老年代回收器不需要整个老年代被回收,一次只需要扫描/回收一小部分的Region就可以了(价值高的Region)

Remembered Set(记忆集)

在这里插入图片描述
由于G1垃圾回收中Region是不可孤立的,一个Region中的对象可能被其他任意Region引用,而回收新生代又要扫描老年代,从而降低了Minor GC的效率

  • 无论G1还是其他分代收集器,JVM都是使用了RemmeberedSet来避免全局扫描
  • 每个Region都有一个对应的RSet
  • 每次Reference类型数据写入操作,都会成圣一个Write Barrier暂停中断操作
  • 然后检查将要写入的引用执行的独享是否金额改Reference类型数据在不同的Region
  • 如不同,通过CardTable把线管引用信息记录到引用指向对象的所在Region对应的Remembered Set中
  • 当进行垃圾收集时,GC根节点的枚举范围加入RS这样就乐意不进行全局扫描也不会有遗漏。

G1回收过程一:年轻代GC

在这里插入图片描述

  • 第一阶段:扫描根
  • 第二阶段:更新RSet
  • 第三阶段:处理RSet
  • 第四阶段:复制对象
  • 第五阶段:处理引用

G1回收过程二:并发标记过程

  1. 初始标记阶段
  2. 根区域扫描(Root Region Scaning)
  3. 并发标记(Concurrent Marking)-
  4. 再次标记(Remark)
  5. 独占清理(cleanup,STW)
    • 这个阶段并不是实际上去做垃圾的收集
  6. 并发清理阶段

G1回收过程三:混合回收

在这里插入图片描述

  • 并发标记结束以后,老年代中百分百为垃圾分段就被回收了,部分为垃圾的内存分段被计算了出来,默认情况,这些老年代的内存分段会分8次被回收
    • -XX:G1MixedGCCountTarget 设置分几次回收
  • 混合回收的回收集(Collection Set)包括八分之一的老年代内存分段,Eden区内存分段,,Surivor区分段。
    • 混合回收的算法那和年轻代回收的算法完全一样,只是回收多了老年代内存分段
  • 由于老年代中内存分段默认分8次回收,G1会优先回收占内存分段比例超过65%的区域
    • -XX:G1MixedGCLiveThresholdPercent 可以设置默认阈值
  • 混合回收不一定要进行8次,有一个阈值默认是10%
    • -XX:G1HeapWastePercentK 可以设置阈值
    • 低于这个阈值就不会被回收

G1回收可选的过程四:Full GC

G1的初衷就是为了避免Full GC的出现。G1会停止应用程序执行(STW)

  • 要避免Full GC的发生,一旦发生需要进行调整,什么事发生Full GC 如:堆内存太小

导致Full GC的原因

  1. Evacuation的时候没有足够的to-space来存放晋升对象
  2. 并发处理过程之前空间耗尽。

G1回收器优化建议

  • 年轻代大小
    • 避免使用-Xmn 或者-XX:NewRatio等相关选项显示的社会组年轻代大小
    • 规定年轻代的大小会覆盖暂停时间目标
  • 暂停事案件目标不要太严苛
    • G1 GC的吞吐量目标是90%的应用程序时间和10%的回收时间
    • 评估G1 GC的吞吐量是,暂停时间目标不要太严苛,目标太过严苛,表示你愿意承担个多的垃圾回收开销,而这些会直接影响到吞吐量

垃圾回收器总结

在这里插入图片描述

怎么选择垃圾回收器?

  1. 优先调整堆代销让JVM自适应完成。
  2. 如果内存小于100m,使用串行收集器
  3. 若是单核、单机程序,没有停顿时间的要求,串行收集器
  4. 如果是多CPU、需要高吞吐、允许停顿时间超过1秒,选择并行(Parallel),或者JVM自适应
  5. 如果各多CPU追求更低地炖,需要快速响应(延迟不超过一秒,如互联内网应用),使用并发收集器(CMS G1)
  6. 官网推介G1,性能高。现在互联网的项目基本都是使用G1

GC日志分析

内存分配与垃圾回收的参数列表

  • -XX:+PrintGC 输出GC日志
  • -XX:+PrintGCDetails 输出GC的详细日志
  • -XX:+PrintGCTimeStamps 输出GC的时间戳
  • -XX:+PrintGCDateStamps 输出GC的时间戳(日期格式)
  • -XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
  • -XLoggc:./logs/gc.log 日志文件的输出路径

新生代垃圾回收图解

在这里插入图片描述

垃圾回收FullGC

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值