JVM的垃圾回收算法有哪些?从可达性分析算法开始,深入解读三大核心垃圾回收算法

导航:

【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/黑马旅游/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码-CSDN博客 

目录

一、概念准备

1.1 GC Roots

1.2 可达性分析算法

1.3 非可达对象被回收过程中的两次标记

1.4 finalize()方法

二、垃圾回收算法

2.1 标记清除算法(Mark-Sweep)

2.2 标记复制算法(Copying) 

2.3 标记整理算法(Mark-Compact) 

2.4 小结:三种垃圾回收对比

三、分代收集理论


 

一、概念准备

1.1 GC Roots

GC Roots:即GC根节点集合,是一组必须活跃的引用,这些对象一定是存活的,不需要被垃圾回收器回收。GC即Garbage Collection,译为垃圾回收。

可作为GC Roots的对象:

  • 本地方法栈中引用的对象:即由本地方法中的局部变量所引用的对象。本地方法栈用于管理本地方法的调用,这些本地方法底层是用C语言写的,被编译为基于本机硬件和操作系统的程序,它们引用的对象不会被回收,所以是GC Root。
  • 虚拟机栈中引用的对象:虚拟机栈又称Java方法栈,即由 Java 方法中的局部变量所引用的对象。虚拟机栈中的局部变量、参数以及返回值等都是直接引用对象的,他们是明确的、在程序运行期间被访问到的对象,所以它们也是GC Root。
  • 方法区中常量、静态变量引用的对象:常量不可变、静态变量先于对象产生,被所有该类的对象共享,所以它们都可认为是GC Root。final修饰的类不可被继承、方法不可被重写、变量不可变。静态变量是类变量,优先于对象产生。常量、静态变量都存放在JVM方法区的类常量池中,类常量池在类加载阶段从Java字节码文件中解析。
  • 所有被同步锁持有的对象:在 Java 虚拟机中,线程持有同步锁时,该对象就是被线程所引用的,即使在程序的其他部分没有对该对象进行引用,它也不会被回收。
  • 所有线程对象:线程对象是程序执行的基本单位之一,他们被创建后,会一直存在直到它们被显式地销毁或者程序运行结束。
  • 所有跨代引用对象:年轻代和老年代之间相互引用的对象成为跨带引用对象。
  • JVM内部的引用:如基本数据类型对应的Class对象,常驻的异常对象(如空指针异常、参数不合法异常,他们在JVM启动过程中就被加载,因为它们太常用了),以及应用程序类类加载器; 

回顾JVM内存模型:

对JVM内存模型(方法区、堆、栈等元素) 不熟悉,可以参考下文:

什么是JVM的内存模型?详细阐述Java中局部变量、常量、类名等信息在JVM中的存储位置-CSDN博客

 

1.2 可达性分析算法

以所有GC Roots为起始点,根据引用关系向下搜索,将所有与GC Roots直接或间接有引用关系的对象在对象头的Mark Word里标记为可达对象,即不需要回收的有引用关系对象。搜索过程所走过的路径称为“引用链” 。

例如下面对象1,2,3,4都在GC Roots的引用链上,所以它们都是可达对象,不会被回收。而对象5,6,7因为没有跟任何一个GC Root直接或间接产生调用关系,所以它们是非可达对象,需要被回收。

 

1.3 非可达对象被回收过程中的两次标记

非可达对象被回收需要两次标记:

  1. 第一次标记后筛选非可达对象:
    1. 非可达对象第一次被可达性分析算法标记后,会进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,也就是是否有机会自救。
    2. 假如对象没有覆盖或者已被JVM调用过finalize()方法,也就是说不想自救或已自救过,那么此对象需要被回收;
    3. 假如对象覆盖并没被JVM调用过finalize()方法,该对象将会被放置在一个名为F-Queue的队列之中,并在稍后由一条由虚拟机自动建立的、低调度优先级的Finalizer线程去执行它们的finalize()方法,完成自救后它们就不会被回收。
  2. 第二次标记F-Queue里的未自救对象:
    1. 稍后,收集器将对F-Queue中的对象进行第二次小规模的标记。
    2. 如果对象要在finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this)赋值给某个引用类型的类变量或者对象的成员变量,那在第二次标记时它将被移出“即将回收”的F-Queue。如果对象这时候还没有逃脱,那它就真的要被回收了。

1.4 finalize()方法

Java中,任何对象都直接或间接是Object类的子类,finalize()是Object类中的一个方法,所以可以说,Java中任何类都有一个finalize()方法。finalize()方法是对象逃脱死亡命运的最后一次机会。

需要注意的是,任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临下一次回收,它的finalize()方法不会被再次执行。

另外,finalize()方法的运行代价高昂,不确定性大,无法保证各个对象的调用顺序,如今已被官方明确声明为不推荐使用的语法。

finalize()方法所在位置:

二、垃圾回收算法

2.1 标记清除算法(Mark-Sweep)

步骤: 

  1. 标记、清除:当堆中有效内存空间被耗尽时,会STW(stop the world,暂停其他所有工作线程),然后先标记,再清除。
  2. 标记:可达性分析法,从GC Roots开始遍历,找到可达对象,并在对象头中进行标记。
  3. 清除:堆内存内从头到尾进行线性遍历,“清除”非可达对象。注意清除并不是真的置空,垃圾还在原来的位置。实际是把垃圾对象的地址维护在空闲列表,对象实例化的申请内存阶段会通过空闲列表找到合适大小的空闲内存分配给新对象。

 

对象头: Hotspot 虚拟机中每个对象都有一个对象头(Object Header),包含Mark Word(标记字段) 和 Class Pointer(类型指针)。

  • Mark Word(标记字段):存储哈希码、GC分代年龄、锁信息、GC标记(标志位,标记可达对象或垃圾对象)等。锁信息包括:
    • 锁标志位:64位的二进制数,通过末尾能判断锁状态。01未锁定、01可偏向、00轻量级锁、10重量级锁、11垃圾回收标记
    • 偏向锁线程ID、时间戳等;
    • 轻量级锁的指针:指向锁记录的指针
    • 重量级锁的指针:指向Monitor锁的指针
  • 类型指针:指向它的类元数据的指针,用于找到对象所在的类。  

 

优缺点和使用场景:

  • 优点:简单
  • 缺点:
    • 效率不高:需要可达性遍历和线性遍历,效率差。
    • STW导致用户体验差:GC时需要暂停其他所有工作线程,用户体验差。
    • 有内存碎片,要维护空闲列表:回收垃圾对象后没有整理,导致堆中出现一块块不连续的内存碎片。
  • 适用场景:适合小型应用程序,内存空间不大的情况。应用程序越大越不适用这种回收算法。

 

2.2 标记复制算法(Copying) 

在开始阶段,将内存空间分为两块,每次只使用一块。 

步骤:  

  • 标记:在进行垃圾回收时,先可达性分析法标记可达对象。
  • 复制:然后将可达对象复制到没有被使用的那个内存块中;
  • 清除:最后清除旧内存块中的所有对象。、
  • 后续再按同样的流程来回复制和清除。

 

优缺点和使用场景:

  • 优点:
    • 垃圾多时效率高:只需可达性遍历,效率很高。
    • 无内存碎片:因为有移动操作,所以内存规整。
  • 缺点:
    • 内存利用率低,浪费内存:始终有一半以上的空闲内存。
    • 需要调整引用地址:可达对象移动后,内存地址发生了变化,需要调整所有引用,指向移动后的地址。
    • 垃圾少时效率相对差,但还是比其他算法强:如果可达对象比较多,垃圾对象比较少,那么复制算法的效率就会比较低。只为了一点垃圾而移动所有对象未免有些小题大做。所以垃圾对象多的情况下,复制算法比较适合。
  • 适用场景:
    • 适合垃圾对象多,可达对象少的情况,这样复制耗时短。
    • 非常适合新生代的垃圾回收,因为新生代要频繁地把可达对象从伊甸园区移动到幸存区,而且是新生代满了适合再Minor GC,垃圾对象占比高,所以回收性价比非常高,一次通常可以回收70-90%的内存空间,现在的商业虚拟机都是用这种GC算法回收新生代。

2.3 标记整理算法(Mark-Compact) 

步骤:   

  • 标记:首先可达性分析法标记可达对象;
  • 整理:然后将可达对象按顺序整理到内存的一端;
  • 清除:最后清理边界外的垃圾对象。

标记整理算法相当于内存碎片优化版的标记清楚算法,不用维护空闲列表。

 

优缺点和使用场景:

  • 优点:
    • 无内存碎片:内存规整。
    • 内存利用率最高:内存既规整又不用浪费一般空间。
  • 缺点:
    • 效率最低:效率比其他两种算法都低
    • 需要调整引用地址:可达对象移动后,内存地址发生了变化,需要调整所有引用,指向移动后的地址。
    • STW导致用户体验差:移动时需要暂停其他所有工作线程,用户体验差。

2.4 小结:三种垃圾回收对比

标记清除算法标记复制算法标记整理算法
速度中等最快最慢
时间开销mark阶段与存活对象的数量成正比,sweep阶段与整堆大小成正比与存活对象大小成正比mark阶段与存活对象的数量成正比,compact阶段与整堆大小成正比,与存活对象的大小成正比
空间开销少(但会堆积碎片)通常需要存活对象的2倍大小(不堆积碎片)少(不堆积碎片)
移动对象

 

三、分代收集理论

垃圾回收器都不会只选择一种算法,JVM根据对象存活周期的不同,将内存划分为几块。一般是把堆分为新生代和老年代,根据年代的特点来选择最佳的收集算法。

分代收集算法:将堆分为新生代、老年代不同生命周期的对象放在不同的代,采用不同的收集算法,以提高回收效率。 

HotSpot 中大部分垃圾回收器都采用分代回收的思想,即新生代用一种垃圾回收算法,老年代用一种垃圾回收算法。

以JDK8为例,JDK8默认回收器是Parallel+Parallel Old,新生代用Parallel回收器,老年代用Parallel Old回收器。

回收过程:

  1.  首先,任何新对象都分配到 eden 空间。两个幸存者空间开始时都是空的。
  2. 当 eden 空间填满时,将触发一个Minor GC(年轻代的垃圾回收,也称为Young GC),删除所有未引用的对象,大对象(需要大量连续内存空间的Java对象,如那种很长的字符串)直接进入老年代。
  3. 所有被引用的对象作为存活对象,将移动到第一个幸存者空间S0,并标记年龄为1,即经历过一次Minor GC。之后每经过一次Minor GC,年龄+1。GC分代年龄存储在对象头的Mark Word里。
  4. 当 eden 空间再次被填满时,会执行第二次Minor GC,将Eden和S0区中所有垃圾对象清除,并将存活对象复制到S1并年龄加1,此时S0变为空。
  5. 如此反复在S0和S1之间切换几次之后,还存活的年龄等于15的对象(JDK8默认15,JDK9默认7,-XX:InitialTenuringThreshold=7)在下一次Minor GC时将放到老年代中。 
  6. 当老年代满了时会触发Major GC(也称为Full GC),Major GC 清理整个堆 – 包括年轻代和老年代。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/696446.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++】 string类:应用与实践

💞💞 前言 hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹 💥个人主页&#x…

vue + element-plus 开发中遇到的问题

1.问题之路由守卫 初写路由守卫,对于next()的理解不是很透彻,就想着都放行,不然看不到效果,结果控制台出现了警告,想着报黄的问题就不是问题,但仔细一看发现他说,如果再生产阶段就会失败&#x…

pytest(二)

1.pytest-html⽣成报告 Pytest-HTML 是⼀个插件,它可以⽣成漂亮且易于阅读的 HTML 测试报告。下⾯是使⽤ pytest-html ⽣成报告的步骤: 1. 安装 pytest-html 插件: pip install pytest-html 2. 运⾏测试并⽣成报告 pytest --htmlr…

老黄终于不穿皮衣了,分享一个AI换装AI试衣软件!

用AI实现在线试衣,或者在线换装,这不是一个新概念,肯定有人这么想过,但并不是所有人能都能轻松做到啊! 今天就来分享一个人人都可以实现的方法,而且是那种傻瓜式的不用付钱的那种,甚至可以把软件…

《系统架构设计师教程(第2版)》第10章-软件架构的演化和维护-07-软件架构维护

文章目录 1. 软件架构知识管理1.1 概念1.2 架构知识的获取1.3 作用1.4 架构知识管理的现状 2 软件架构修改管理3 软件架构版本管理4. 示例4.1 背景4.2 数据获取4.3 数据计算4.4 结果分析4.4.1 圈复杂度 (CCN)4.4.2 扇入扇出度 (FFC)4.4.3 模块间耦合度 (CBO)4.4.4 模块的响应 (…

深入探讨黑盒测试:等价类划分与边界值分析

文章目录 概要黑盒测试等价类划分边界值分析 设计测试用例小结 概要 在软件开发领域,测试是确保产品质量的关键步骤之一。而黑盒测试方法作为其中的一种,通过关注输入与输出之间的关系,而不考虑内部实现的细节,被广泛应用于各种软…

Python爬虫入门:网络世界的宝藏猎人

今天阿佑将带你踏上Python的肩膀,成为一名网络世界的宝藏猎人! 文章目录 1. 引言1.1 简述Python在爬虫领域的地位1.2 阐明学习网络基础对爬虫的重要性 2. 背景介绍2.1 Python语言的流行与适用场景2.2 网络通信基础概念及其在数据抓取中的角色 3. Python基…

SpringSecurity6实现动态权限,rememberMe、OAuth2.0授权登录,退出登录等功能

本文章对应视频可在B站查看SpringSecurity6对应视频教程,记得三连哦,这对我很重要呢! 温馨提示:视频与文章相辅相成,结合学习效果更强哦! 系列文章链接 1、初识SpringSecurity,认识主流Java权限…

代码大师的工具箱:现代软件开发利器

✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭❤~✨✨ 🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢,在这里我会分享我的知识和经验。&am…

深入理解与应用C++ Vector

1. C Vector 简介与基本使用 C 的 vector 是一个序列容器,用于表示可变大小的数组。它结合了数组的高效元素访问和动态大小调整的灵活性。与静态数组相比,vector 的大小可以根据需要自动调整,这是通过在底层使用动态数组来实现的。当新元素被…

SpringSecurity6集成数据库

本文章对应视频可在B站查看SpringSecurity6对应视频教程,记得三连哦,这对我很重要呢! 温馨提示:视频与文章相辅相成,结合学习效果更强哦! 系列文章链接 1、初识SpringSecurity,认识主流Java权限…