当前位置: 首页 > article >正文

JVM的GC算法以及常见垃圾回收器

针对简历技能:熟悉JVM的GC算法、常见垃圾回收器

针对 HotSpot VM 的实现,它里面的 GC 其实准确分类只有两大种:

部分收集 (Partial GC):

  • 新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集;
  • 老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。
  • 混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。

整堆收集 (Full GC):收集整个 Java 堆和方法区。

死亡对象判断方法

引用计数法

​ 给对象添加一个引用计数器:

  • 每当有一个地方引用它,计数器就+1
  • 当引用失效,计数器就-1
  • 任何时候计数器为0的对象就是不可能再被使用的

存在问题:很难解决对象之间循环引用的问题。

public class ReferenceCountingGc {
    Object instance = null;
    public static void main(String[] args) {
        ReferenceCountingGc objA = new ReferenceCountingGc();
        ReferenceCountingGc objB = new ReferenceCountingGc();
      // 建立了一个对象间的引用关系,即 objA 持有对 objB 的引用
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
    }
}

所谓对象之间的相互引用问题,就是objAobjB相互引用对象,导致它们的引用计数器都不为0,于是引用计数器无法通知GC回收该对象。

可达性分析法

思路:通过一系列的称为“GC Roots”的对象作为起点,向下搜索,节点所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。

下图的Obj1 ~ Obj2 之间虽然有引用,但它们不可达Roots,因此需要被回收。

在这里插入图片描述

哪些对象可以作为GC Roots?

  • 虚拟机栈(栈帧中的局部变量表)中引用的对象

  • 本地方法栈(Native 方法)中引用的对象

  • 方法区中类静态属性引用的对象

  • 方法区中常量引用的对象

  • 所有被同步锁持有的对象

  • JNI(Java Native Interface)引用的对象

可以被回收,就代表一定会被回收吗?

被判定需要执行的对象,将会被放在一个队列中进行二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则会被真的回收。

HotSpot 为什么要分为新生代和老年代

目的:优化垃圾回收机制,优化内存回收策略,针对不同生命周期的对象采用不同的回收算法,减少垃圾回收的频率和时间开销,避免内存碎片。

在Java程序中,对象的生命周期通常分为两种:

  • 新生代:局部变量或临时对象,他们的生命周期很短,创建后很快就会被回收。
  • 老生代:全局对象或程序核心逻辑相关的长期数据。

不同生命周期的对象需要不同的垃圾回收策略:

  • 新生代采用复制算法
  • 老年代采用标记-清除 / 标记-整理算法

如何判断一个类是无用的类?

条件:

  • 该类所有的实例都已经被收回,也就是说Java堆中不存在该类的任何实例
  • 加载该类的ClassLoader已经被回收。
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

垃圾回收算法(重点)

1、标记-清除算法

  • 概念:标记处所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。

  • 实现:

    • 1、当一个对象被创建时,给一个标记位,假设是0(false)
    • 2、在标记阶段,将所有用户引用的对象标记为1(true)
    • 3、扫描阶段清除标记位为0的对象
  • 存在问题;1、标记和清除两个过程效率不高。2、标记清除后会产生大量不连续的内存碎片。

2、复制算法(对半回收)

  • 将内存分为大小相同的两块,每次只使用其中的一块。当其中一块内存使用完之后,就将还存活的对象复制到另一块去,然后再把使用的空间一次性清理掉。
  • 存在问题:1、可用内存变小。2、不适合老年代(存活第一项数量比较大,复制性能差)

3、标记-整理算法

  • 标记-整理(Mark-and-Compact)算法是根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
  • 存在问题:由于多了整理这一步,因此效率也不高,适合老年代这种垃圾回收频率不是很高的场景。

4、分代收集算法

  • 比如在新生代中,每次收集都会有大量对象死去,所以可以选择“复制”算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。

HotSpot 为什么要分为新生代和老年代?

新生代:用于存放短期存在的对象。大部分新创建的对象会首先分配到新生代。新生代的GC通常采用Minor GC,它是频繁而快速的,因为大部分对象都会很快被回收。

老年代:存放那些经过多次GC依然存活的对象,这些对象生命周期较长。老年代的GC称为Major GC(或Full GC),相较于Minor GC,执行频率较低但开销更大。

新生代采用复制算法:新生代通常采用复制算法,内存分为两个区域(Eden区和Survivor区),每次GC会将存活对象复制到Survivor区或直接提升到老年代。这种算法能够快速清除新生代中大量的短命对象。

老年代采用标记-整理算法:老年代的对象生命周期较长,采用标记-清除或标记-压缩算法,这样能减少内存碎片,保证长期存活对象的内存利用率。

常见的垃圾回收器(重点)

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。

JDK 默认垃圾收集器(使用 java -XX:+PrintCommandLineFlags -version 命令查看)

Serial(串行)收集器

  • 单线程收集器,使用一条垃圾收集线程完成垃圾收集工作,同时暂停其他工作线程,直到收集结束。

    新生代采用标记-复制算法,老年代采用标记-整理算法。

  • 应用场景:运行在CLient模式下的虚拟机(本地运行,而不是依赖远程服务器)

ParNew收集器

  • Serial的多线程版本

并行(Parallel):多条垃圾回收线程并行工作,但此时用户线程仍然处于等待状态。

并发(Concurrent):用户线程与垃圾收集线程同时执行(交替/并行)

Parallel Scavenge收集器

使用标记-复制算法的多线程收集器

Parallel Scavenge 和 CMS 收集器的区别:PS收集器提供了多个参数供用户找到最合适的停顿时间或最大吞吐量(吞吐量:CPU中运行用户代码的时间 和 CPU总消耗时间的比值)

新生代采用标记-复制算法,老年代采用标记-整理算法。

Serial Old收集器

Serical 单线程老年版本收集器

用途:CMS收集器的后备方案。

Parallel Old 收集器

Parallel Scavenge 收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。

CMS(Concurrent Mark Sweep)收集器

目的:获取最短回收停顿时间

CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。

步骤:

初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;

并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。

重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。

并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。

CMS 收集器

主要优点:并发收集、低停顿

缺点:

  • 对 CPU 资源敏感;
  • 无法处理浮动垃圾;
  • 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。

JDK9中过时,JDK14中移除

G1收集器(重点)

G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.

特点:

  • 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
  • 分代收集:虽然 G1 不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
  • 空间整合:与 CMS 的“标记-清除”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。
  • 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒。

步骤:

  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收

G1垃圾回收周期图示

G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region 。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。

JDK9开始,G1成为了默认的垃圾收集器。

ZGC收集器

特点:

  • 停顿时间不超过10ms;
  • 停顿时间不会随着堆的大小,或者活跃对象的大小而增加;
  • 支持8MB~4TB级别的堆

ZGC 在 Java11 中引入,处于试验阶段。经过多个版本的迭代,不断的完善和修复问题,ZGC 在 Java15 已经可以正式使用了。

ZGC垃圾回收周期

关于 ZGC 收集器的详细介绍推荐阅读美团技术团队的 新一代垃圾回收器 ZGC 的探索与实践 这篇文章


http://www.kler.cn/a/349750.html

相关文章:

  • 中国股市“慢牛”行情的实现路径与展望
  • buu-pwn1_sctf_2016-好久不见29
  • SQL注入漏洞之绕过[前端 服务端 waf]限制 以及 防御手法 一篇文章给你搞定
  • 基于Python的人工智能患者风险评估预测模型构建与应用研究(下)
  • GSI快速收录服务:让你的网站内容“上架”谷歌
  • 16.Word:石油化工设备技术❗【28】
  • 多级缓存-
  • HTML5实现古典音乐网站源码模板1
  • 分布式数据库:构建高效、可靠的数据存储与管理系统
  • 观察者模式的思考
  • CSS 实战录: 双栏、四等分、不等间距、自适应...
  • STL --- list(C++)
  • 线程局部存储(TLS)
  • C++11的新特性
  • 【C++算法】10.滑动窗口_无重复字符的最长子串
  • JSON 详解
  • JavaScript 数据类型
  • uniapp做的app实现首页左滑退出应用
  • 深度学习 | Pytorch的GPU版本查看GPU是否可用、GPU版本、GPU数量
  • 13.3寸三防平板大尺寸+高速运行提升工业软件操作体验
  • 数据结构之顺序表——动态顺序表(C语言版)
  • linux和端口相关的命令总结
  • JAVA软开-面试经典题(7)-字符串常量池
  • Flutter打包错误解决指南
  • [Leetcode] 560 Subarray Sum Equals K
  • K8s简介及环境搭建