目录

1、垃圾回收概述

1.1、什么是垃圾

1.2、为什么需要GC

1.3、早起垃圾回收机制

2、垃圾回收的相关算法

2.1、标记阶段:引用计数算法

2.2、标记阶段:可达性分析算法

2.3、对象的finalization机制

2.4、清除阶段:标记-清除算法

2.5、清除阶段:复制算法

2.6、清除阶段:标记-压缩算法

2.7、分代收集算法

2.8、增量收集算法、分区算法

3、垃圾回收的相关概念

3.1、System.gc()的理解

3.2、内存溢出和内存泄漏

3.3、Stop The World

3.4、垃圾回收的并行和并发

3.5、安全点和安全区域

3.6、Java中的引用

3.6.1、强引用

3.6.2、软引用

3.6.3、弱引用

3.6.4、虚引用

3.6.5、终接器引用

4、垃圾回收器

4.1、GC分类与性能指标

4.2、Serial回收器:串行回收

4.3、ParNew回收器:并行回收

4.4、Parallel回收器:吞吐量优先

4.5、CMS回收器:低延迟

4.6、G1回收器:区域分化代式


PDF版笔记:JVM的学习笔记PDF版-互联网文档类资源-CSDN下载

1、垃圾回收概述

1.1、什么是垃圾

在运行程序中没有任何指针指向的对象,如果垃圾不被回收,那么这些垃圾所占的空间会一直保留到应用程序结束,且被保存的空间无法被其他对象使用,甚至可能导致内存溢出

1.2、为什么需要GC

  • 如果不进行垃圾回收,内存迟早会被消耗完
  • 清除内存里的记录碎片,以便JVM将整理出来的内存分配给新的对象
  • 随着应用程序所应付的业务越来越大、复杂,没有GC就不能保证应用程序的正常进行
  • 频繁的申请和释放内存会给开发人员带来管理负担,而且有可能会产生内存泄漏,随着系统运行时间,造成内存溢出和程序崩溃

1.3、早起垃圾回收机制

优势:

  • 有自动内存管理,则无需开发人员手动参与内存的分配和回收,降低了内存泄漏和内存溢出的风险
  • 可以将开发人员从繁重的内存管理中解放出来,可以更专心于业务开发

不足:

  • 过度依赖自动内存管理,就会弱化Java开发人员在程序出现内存溢出时定位内存和解决问题的能力
  • 我们需要对自动内存管理的技术实施必要的监控和调节

Java堆是垃圾回收的重点区

  • 频繁回收新生区
  • 较少回收老年区
  • 基本不回收方法区

2、垃圾回收的相关算法

2.1、标记阶段:引用计数算法

原理:对每个对象保存一个整形的引用计数器属性。用于记录对象被引用

优点:

  • 实现简单,垃圾对象便于辨识
  • 判定效率高,回收没有延迟性

缺点:

  • 需要单独的字段存储计数器,增加内存空间的开销
  • 每次赋值和更新计数器,伴随着加减的操作,增加了时间开销
  • 无法处理循环引用的情况,所以导致Java的垃圾回收器中没有使用该方法

使用该算法如何解决器问题:

  • 手动解除:在合适的时机,解除引用关系
  • 使用弱引用weakref,weakref是Python提供的标准库,旨在解决循环引用

2.2、标记阶段:可达性分析算法

又名:根搜索算法、追踪性垃圾收集

原理:

  • 根据根对象集合GC Roots(一组必须活跃的引用)为起始点,按照从上到下搜索被GC Roots所连接的目标对象是否可达
  • 使用可达性分析算法后,内存中存活的对象都会被GC Roots直接或间接连接这,搜索所走过的路径被称为引用链
  • 如果目标对象没有任何引用链相连,则是不可达,即对象已经死亡,标记为垃圾对象

在Java语言中,GC Roots包括一下几类元素:

  • 虚拟机栈中引用的对象:如各个线程被调用的方法中使用到的参数、局部变量
  • 本地方法栈neiJNI引用的对象
  • 方法区中静态属性、常量引用的对象
  • 被同步所以synchronized持有的对象
  • JVM内部的引用:基本数据类型对应的Class对象、常驻的异常对象、系统类加载器
  • 反映JVM内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
  • 除了这些固定的GC Roots集合外,根据用户所选用的垃圾收集器已经当前回收的内存区域不同,还可以有其他对象“临时性”地加入。比如:分代收集会局部回收
  • 如果一个指针,它保存了堆内存里面的对象,但字节又不存放在堆内存里面,那它就是一个Root

优点:解决了循环引用的问题

缺点:导致GC进行时必须STW

2.3、对象的finalization机制

  • Java语言提了对象终止机制来允许开发人员提供对象被销毁之前的自定义处理逻辑
  • 当垃圾回收器发现没有引用指向一个对象,垃圾回收器会调用finalize()方法
  • finalize()方法允许子类重写,用于在对象在被回收时进行资源的释放,且finalize()方法只能被调用一次
  • 不要主动调用finalize()方法:因为,①可能会导致对象的复活。②finalize()方法的执行时间没有保障,它完全由GC线程决定。③一个糟糕的finalize()会严重影响GC的性能。
  • 由于finalize()方法存在,虚拟机对象可能存在三个状态:①可触及的 ②可复活的 ③不可触及的

2.4、清除阶段:标记-清除算法

原理:

  • 当堆中有效的内存空间被耗尽的时候,就会停止整个程序
  • 标记:Collector从根节点开始遍历,标记所以被引用的对象。一般是在对象的Header’中记录为可达对象
  • 清除:Collector队堆内存从头到尾进行线性的遍历,如果发现某个对象在器Header中没有标记为可达对象,将其回收

优势:简单直接、容易理解

缺点:

  • 效率不高
  • 在进行GC时,需要停止整个应用的进程,导致用户体验差
  • 会产生内存碎片,还需要维护一个空闲列表,占用更多的内存资源

2.5、清除阶段:复制算法

目的:为了解决标记-清除算法在垃圾回收效率方面的缺陷

原理:

  • 将可用内存分为两部分,每次只使用一部分
  • 如果现在使用A区,需要堆A区进行CG,则把A区所以存活的对象复制到B区(此时B区的对象是排列规整的),再释放A区的所以内存

优势:

  • 没有标记清除的过程,实现简单,运行高效
  • 不会出现内存碎片问题

缺点:

  • 内存的使用率不高
  • 复制对象需要改变对象的地址,各种引用处需要进行调整

特别:如果系统中垃圾对象特别多,则复制的对象数量不会很多,使用复制算法则效率较高

2.6、清除阶段:标记-压缩算法

又名:标记-整理算法、

目的:为了进行内存碎片整理

原理:

  • 标记:其过程与标记清除算法的标记阶段一样
  • 整理:将标记的对象压缩到内存的一端,按照顺序排放,并清除边界外的所有空间

优点:

  • 不会出现内存碎片
  • 内存使用效率高

缺点:

  • 效率要低于复制算法、标记-清除算法
  • 在移动对象的同事,如果对象被其他对象引用,则还需要调整引用的地址
  • 移动的过程中,存在STW

2.7、分代收集算法

原理:不同的对象的生命周期是不一样的,针对不同生命周期的对象采取不同的回收算法进行处理。

年轻区:对象生命周期短,GC执行频繁

采用方法:复制算法

老年区:对象声明周期长,GC执行不频繁

采用方法:标记-清除算法、标记-整理算法

2.8、增量收集算法、分区算法

增量收集算法:

目的:解决STW时间比较长的问题

原理:垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程。依此反复,直到垃圾收集完成。

缺点:频繁的线程切换和上下文转换的消耗,会导致垃圾回收算法的总体成本上升,造成系统吞吐量下降

分区算法:

目的:解决STW时间比较长的问题

原理:将堆空间分成一个个小区间region,每个小区间都独立使用、独立回收

3、垃圾回收的相关概念

3.1、System.gc()的理解

  • 调用该方法显示触发Full GC,但是不能确保其执行时间
  • 一般用于性能测试的性能基准
  • 如果使用System.runFinalization(),则一定会强制其调用GC

3.2、内存溢出和内存泄漏

内存溢出:

  • 定义:没有空闲内存,且垃圾回收器也无法提供更多内存
  • 原因:①JVM堆内存设置不够。②代码中创建了大量大对象,且长时间不能被垃圾回收器回收。
  • 特殊:一般情况下,报OOM之前都会进行一次GC。但是当存在一个超大对象,超过了堆的大小,则报OOM之前不会进行GC

内存泄漏:

  • 定义:当对象不会再被程序用到,但是GC又不能回收他们,则称之为内存泄漏(狭义)。出现很多对象的生命周期变得很长甚至导致OOM,也可以称为内存泄漏(广义)。
  • 内存泄漏可能导致OOM

例子:

  1. 单例模式引用外部的对象,但是使用完成后没有进行置空
  2. 一些提供close的资源未关闭导致内存泄漏(如数据库连接、网络连接)

3.3、Stop The World

定义:应用程序在执行GC过程中,产生的应用程序的停顿

作用:保证数据的一致性,如果分析过程中对象的引用关系还在不断变化,则分析结果准确性无法保证

特点:

  • 所有的GC都存在这个事件
  • STW在JVM后台自动发起和自动完成

3.4、垃圾回收的并行和并发

并发

  • 在操作系统中,指一个时间段中有几个程序都处于已经启动运行到运行完毕之间,且几个程序都是在同一个处理器上运行
  • 并不是真正意义上的同时进行,执行CPU在几个线程之间来回切换

并行:当一个系统有多个处理器(多个CPU或者多核CPU),两个进程互不抢占CPU资源,可以同时进行

垃圾回收的并行

  • 并行:指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态
  • 串行:单线程执行,如果内存不够,则暂停程序,启动JVM垃圾回收器进行垃圾回收。回收完,再启动程序的线程

垃圾回收的并发

  • 指用户线程和垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),垃圾回收线程在执行是不会停顿用户程序的运行

3.5、安全点和安全区域

安全点:程序执行时并非在任何地点都能停顿下来开始GC,只有在特定的位置才能停顿下来开始GC,这样的位置称为安全点。

安全点选择:如果太少可能导致GC等待时间太长,如果太频繁可能导致运行时性能问题。通常会选择一些执行时间较长的指令作为安全点。如方法调用、循环跳转和异常跳转

Q:如何在GC发生时,检查所有线程都跑到最近的安全点停顿下来?

  • 抢断式中断(目前没有使用了):首先中断所以的线程,如果还有线程不在安全点,则恢复线程跑到安全点。
  • 主动式中断:设置一个中断标志,各个线程运行到安全点是主动轮询这个标志,如果中断标志为真,则自己进行中断挂起。

安全区域:指一段代码片段中,对象的引用关系不会发生变化,在这个区域中任何位置开始GC都是安全的。(主要是为了应对如Sleep等不执行的状态,这样无法是线程主动跑到安全点)

实际执行时:

  1. 当线程运行到安全区域的代码时,首先标识已经进入安全区域,如果这段时间内发生GC,JVM会忽略标识为安全区域状态的线程
  2. 当线程即将要离开安全区域,会检查JVM是否正在执行GC,如果不在GC中则继续执行,否则线程必须等待直到接收到可以离开安全区域的信号为止

3.6、Java中的引用

目标:希望存在这样的一种对象,当内存空间还足够的时候,能够将这些对象保存在内存中;如果内存空间在进行垃圾收集后还是很紧张,则抛弃这些对象

结果:在JDK1.2之后,对引用的概念进行了扩充,将引用分为强引用、弱引用、软引用、虚引用。这四种引用强度依此逐渐减弱。其继承了共同父类Reference。

3.6.1、强引用

特点

  • 最传统的“引用”的定义,无论任何情况下,只要强引用关系还存在,垃圾回收器就永远不会回收的引用
  • 强引用时可触及的
  • 强引用是造成内存泄漏的主要原因之一

3.6.2、软引用

特点:

  • 在系统发生OOM之前,会将这类对象列入回收范围,进行二次回收。如果此次回收后,内存还是不足,才会抛出OOM。
  • 通常用来实现内存敏感的缓存,比如高速缓存

3.6.3、弱引用

特点:被弱引用关联的对象,只能生存到下次垃圾回收之前。无论内存空间是否足够,都会被回收。

3.6.4、虚引用

特点:

  • 一个对象释放又虚引用的存在,完全不会影响其生存时间,也无法提供虚引用来获得一个对象的实例。用于其对象回收的跟踪,其唯一存在的目的就是能够在这个对象被收集器回收时收到一个系统通知。

3.6.5、终接器引用

  • 用以实现对象的finalize()方法,也可以称为终结器引用
  • 无需手动编码,其内部配合引用队列使用
  • 在GC时,终接器引用人入队。由Finalizer线程通过终结器引用找到被引用的对象并调用它的finalize()方法,第二次GC时才能回收被引用的对象

4、垃圾回收器

4.1、GC分类与性能指标

GC分类

  • 按照垃圾回收器的线程分:串行式(一个线程使用),并行式(多个线程共同进行)
  • 按照工作模式区分:并发式(垃圾回收器和应用程序线程交替工作,减少了延时性),独占式(存在STW)
  • 按照碎片处理方式区分:压缩式、非压缩式(存在内存碎片)
  • 按照工作的内存区分:年轻代、老年代

评估GC的性能指标

  • 吞吐量:运行用户代码的时间占总运行时间的比例
  • 垃圾收集开销:吞吐量的补数
  • 暂停时间:执行垃圾收集时,程序的工作线程被暂时的时间,STW长度
  • 收集频率:相对于应用程序的执行,收集操作发生的频率
  • 内存占用:Java堆区所占的内存大小
  • 快速:一个对象从诞生到被回收所经历的时间

注:吞吐量、暂停时间、内存占用被称为不可能三角,任何垃圾回收器只能满足其中两项

所有线条:JDK8之前

红色虚线:JDK8里面废弃、JDK9中移除

绿色虚线:JDK 14弃用

青色虚线边框:JDK14 删除CMS垃圾回收器

没有一个完美的收集器,我们只是选择对具体情况应用最合适的收集器

一些参数设置

  • “-XX:+PrintCommandLineFlags” 查看命令行相关参数,包括使用的垃圾回收器
  • “jinfo -flag” 相关垃圾回收器参数 进程ID
  • “-XX:UseParNewGC” 手动开启使用ParNew垃圾回收器
  • “-XX:ParallelGCThreads”限制线程数量,一般与CPU内核数量保存一致即可,即默认情况

4.2、Serial回收器:串行回收

  • 最基本、历史最悠久的垃圾回收器
  • Serial收集器作为Hotspot中Client模式下默认的新时代垃圾收集器
  • Serial收集器采用复制算法、串行回收和STW机制方式执行内存回收
  • Serial收集器还提供了用于执行老年代垃圾收集的Serial Old收集器。Serial Old收集器同样采用串行回收和STW机制,只不过内存回收算法使用标记-压缩算法
  • Serial Old是运行在Client模式下默认的老年代的垃圾回收器
  • Serial Old在Server模式下主要两个用途①与新生代的Parallel Scavenge配合使用。②作为老年代CMS收集器的备用垃圾收集方案

优势:简单而高效(在单线程下)

4.3、ParNew回收器:并行回收

  • ParNew除了采用并行回收的方式执行内存回收外,两款垃圾回收器几乎没有区别
  • ParNew是很多JVM在Server模式下的新生代默认收集器
  • 但是:在JDK9中移除了ParNew与Serial Old的配合使用,在JDK13中,又移除了剩下的唯一一个能与其配合使用的老年代收集器——CMS

4.4、Parallel回收器:吞吐量优先

目标:①达到一个可控制的吞吐量②自适应调节策略

  • 复制算法、并行回收和STW
  • 高吞出量适合后台运算而不需要太多交互的任务。如:批量处理、订单处理、科学计算等
  • Parallel Old收集器,采用标记-压缩算法,并行回收和STW
  • 在JDK8中的默认使用Parallel GC

参数设置:

  • “-XX:UseParallelGC” 使用Parallel GC
  • “-XX:UseParallelOldGC” 使用Parael Old GC(以上两个参数互相激活)
  • “-XX:ParallelGCThreads”设置年轻代并行收集器的线程数 CPU_Count 8, thread = 3 + [ 5 * CPU_Count ]
  • “-XX:MaxGCPaiseMillis” 设置垃圾回收器最大停顿时间,STW时间 慎用
  • “-XX:GCTimeRatio” 垃圾收集时间占总时间比例 与最大停顿时间有一定的矛盾性
  • “-XX:+UseAdaptiveSizePolicy” 设置Parallel Scavenge收集器具有自适应调节策略,
    • 在这种模式下,年轻代的大小、Eden和Servivor的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。
    • 在手动调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量和停顿时间,让虚拟机自己完成调优任务

4.5、CMS回收器:低延迟

  • CMS(Coucurrent-Mark-Sweep)一款强交互应用,适合与用户交互的程序
  • 真正意义上的并发的收集器
  • 采用标记-清除算法,存在STW
  • 在JDK9中被废弃,JDK14被移除了

CMS的工作原理

  1. 初始标记
    1. 程序中所有的工作线程都会STW
    2. 主要任务是标记出CG Roots能够直接关联到的对象,时间较短
  2. 并发标记:从GC Roots的直接关联的对象开始遍历整个对象图的过程,耗时较长,但是没有STW
  3. 重新标记:为了修正并发标记期间,因为用户程序继续运行而导致标记产生变动的那部分对象标记记录,存在STW。比初始标记稍长,但是比并发标记短
  4. 并发清除:清除删除掉标记阶段判断的已经死亡的对象,释放空间

特点

  • 由于最耗时的并发标记与并发清除阶段都没有STW,所以整体延时非常低
  • 由于是并发执行的,所以在CMS回收过程中,还应该保存应用程序用户线程有足够的内存可用。所以CMS不能像其他GC那样等待老年区几乎被天猫再进行收集,而是当堆内存使用率到达了一阈值,便开始回收。
  • 如果CMS运行期间预留的内存不足,则会出现溢出“Concurrent Mode Failure”,并临时启动Serial Old收集器重新对老年区进行垃圾收集(同时进行碎片整理)
  • 因为是并发的线程,所以只能使用标记-清除算法

优点

  • 低延时
  • 并发收集

缺点

  • 产生内存碎片,不得不提及进入Full GC
  • CMS收集器堆CPU资源非常敏感
  • CMS收集器无法处理浮动垃圾。即在并发标记阶段产生的新的垃圾对象,CMS无法对这些垃圾对象进行标记,最终会导致这些新的垃圾对象没有及时回收。

设置参数

  • “-XX:+UseConcMarkSweepGC” 手动设置使用CMS收集器(自动打开“-XX:+UserParNewGC”)
  • “-XX:CMSlnitiatingOccuoanyFraction” 设置堆内存使用率的阈值(JDK5 默认68 JDK6 默认 92)
  • “-XX:+UseCMSCompactAtFullCollection” 设置指定在执行完Full GC后对内存空间进行压缩整理
  • “-XX:CMSFullGCCsBeforeCompaction” 设置在执行多少次Full GC后对内存空间进行整理压缩
  • “-XX:ParallelCMSThreads” 默认:(并行的线程数 + 3)/ 4

4.6、G1回收器:区域分化代式

目标:在延迟可控的情况下获取获取尽可能最高的吞吐量

为什么叫Garbage-First?

  • G1是一个并行回收器,它吧堆内存分割为很多不相关的区域(Region)。使用不同的Region来表示Eden、S0、S1、老年代等
  • G1 GC有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值的大小(回收所获得的空间大小以及回收所需要时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region
  • 由于这种方式的侧重在于回收垃圾最大量的区间,所以成为了:垃圾优先(Garbage First)

特点:

  • JDK1.7加入,JDK9后设置为默认垃圾回收器
  • 面向服务端应用的垃圾收集器,主要针对配合多核CPU及大容量内存的机器

优势

  • 并行与并发
    • 并行性:G1在回收期间,可以多个GC线程同时工作,此时存在STW
    • 并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行
  • 分代收集
    • G1仍然属于分代型垃圾回收器,但是不再坚持固定大小和固定数量
    • 将堆空间分为若干区域(Region),这些区域中包含了逻辑上的年轻代和老年代
    • 它同时兼顾了年轻代和老年代
  • 空间整合
    • Region之间是复制算法
    • 从整体上看是标记-压缩算法
  • 可预测的停顿时间模型
    • 根据限定的时间,根据优先列表,优先回收价值最大的Region
    • G1的延时停顿上限不一定比CMS延时停顿的上限高,但是G1的延时停顿下限比CMS延时停顿的下限高

缺点

  • 在用户程序运行过程中,G!的垃圾回收产生的内存占用和运行时的额外执行负载逗比CMS高
  • 在小内存上,CMS的表现大概率比G1优秀;而G1在内存情况下比CMS优秀;平衡点在6-8G

G1回收器的参数

  • “-XX:+UseG1GC” 开启G1(GDK8之前)
  • “-XX:G1HeapRegionSize” 设置每个Region的大小,指为2的幂,1~32MB(建议设置)
  • “-XX:MaxGCPauseMillis”设置期望最大GC停顿时间指标(尽量)默认200ms(建议设置)
  • “-XX:ParallelGCThread”设置STW工作线程数的指,最大为8
  • “-XX:ConcGCThreads” 设置并发标记的线程数,一般将其设置为并行垃圾回收线程数的四分之一左右
  • “-XX:InttiatingHeapOccupancyPercent”设置出发并发GC周期的Java堆占用的阈值,超过此值,触发GC,默认45

使用场景

  • 服务器端,针对大内存、多处理器的机器
  • 最主要的应用是需要低GC延迟,并具有大堆的应用程序提供解决方案。如:堆大小约6GB或更大,可预测的暂停时间可以低于0.5秒
  • 用来替换JDK1.5中的CMS收集器
    • 超过50%的Java堆被获得数据占用
    • 对象分配频率或年代提升频率变化很大
    • GC停顿时间过长(长于0.5~1秒)
  • Hotspot垃圾收集器中,除了G1外,其余垃圾收集器使用内置的JVM线程执行GC的多线程操作,而G!可以采用应用线程承担后台运行的GC工作,即当JVM的GC线程处理速度慢时,系统用应用程序线程帮助加速垃圾回收过程

分区Region

  • 一个Region在存活期间只能是一个角色,但是被清除内存后可以改变角色
  • G1垃圾收集器增加了一种新的内存区域,叫做Humongous内存区域,主要用于存储大对象。如果超过1.5个region就放入到H
    • 如果一个H区装不下一个大对象,那么G1会寻找连续的H区来存储,有时会触发Full GC。大多数时候吧H区当为老年区来看

Rememnered Set(记忆集)

  • 存在问题:需要回收一个区域的对象,需要考虑其对象是否被其余区域的对象引用。也就是说,回收新生代也不得不同时扫描老年代,这样就会导致Minor GC的效率降低
  • 解决方法
    • 给每个Region配一个记忆集
    • 每次Reference类型数据写操作时,都会产生一个Write Barrier暂停中断操作
    • 检查要写入的对象指向的对象释放和该Reference类型数据在不同的Region
    • 如果不同,通过CardTable把相关引用信息记录到引用指向对象的所在Region读音的Remembered Set中
    • 当进行垃圾回收集时,在GC根节点的枚举范围加入Remembed Set;就可以保证不进行全局扫描也不会有遗漏

G1垃圾回收过程

  1. 年轻代GC:当eden区用尽时,开始年轻代回收过程:
    1. 扫描根:根引用连同RSet记录的外部引用作为扫描入口。
    2. 更新RSet:处理dirty card queue中的card,更新RSet。此阶段完成后。RSet可准确反映老年代队所在内存分段中对象的引用
    3. 处理RSet:识别被老年代对象所指向的Eden中的对象,这些被指向的Eden的对象被认为时存货的对象。
      1. 对应于应用程序的引用赋值语句object.field = object,JVM会在之前后之后执行特殊的操作以在dirty card queue中入队一个保持了对象引用信息的card。在年轻代回收的时候,G1会对Dirty Queue中所有的card进行处理,保证RSet实时准确的反映引用关系
    4. 复制对象:复制算法
    5. 处理引用:处理各种等级的引用
  2. 老年代并发标记过程:当堆内存达到一定阈值时,开始老能得到并发标记过程
    1. 初始阶段标记:标记从根节点直接可达的对象,存在STW,且触发一次年轻代GC
    2. 根区域扫描:G1 GC扫描Survivor区域直接可到的老年区域对象,并标记被引用的对象,在年轻代GC之前
    3. 并发标记:在整个堆中进行并发标记,这个过程可能被年轻代GC中断。在并发标记阶段,存在实时回收——若发现区域对象中所有的对象都是垃圾,那这个给区域就会被立即回收。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)
    4. 再次标记:由于应用程序持续进行,需要修改上次的标记结果,存在STW。比CMS使用了更快的ASTB(snapshot-at-the-beginning)算法
    5. 独占清除:计算各个区域的存货对象和GC回收比,并进行排序,识别可以混合回收的区域,存在STW
    6. 并发清除阶段:识别并清除完全空闲的区域
  3. 混合回收:从老年代移动存活的对象放到空闲区,该区成为新的老年代,只会扫描回收老年代的一部分
    1. 默认老年代的内存分8次被回收(可设置)
    2. 垃圾占比越高,越先被回收
    3. 混合回收不一定进行8次,如果发现可回收的垃圾占堆内存的比例低于某个阈值(默认10%,可设置),则不在进行混合回收。
  4. 如果需要,可能会执行Full GC,这是针对GC的评估失败提供了一种失败保护机制,即强力回收