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 清理整个堆 – 包括年轻代和老年代。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

单页源码加密屋zip文件加密API源码

简介: 单页源码加密屋zip文件加密API源码 api源码里面的参数已改好,往服务器或主机一丢就行,出现不能加密了就是加密次数达到上限了,告诉我在到后台修改加密次数 点击下载

基于SpringBoot + Vue的学生宿舍课管理系统设计与实现+毕业论文(15000字)+开题报告

系统介绍 本系统包含管理员、宿管员、学生三个角色。 管理员:管理宿管员、管理学生、修改密码、维护个人信息。 宿管员:管理公寓资产、管理缴费信息、管理公共场所清理信息、管理日常事务信息、审核学生床位安排信息。 学生:查看公共场所清理…

初识C语言——第十九天

for循环 1.简单概述 2.执行流程 3.建议事项:

docker 安装 Redis (附图文教程)

首先确保已安装docker 安装docker 拉取 redis 镜像 搜索镜像 docker search redis使用最多人使用的 拉取镜像 没有指定版本默认最新版本 docker pull redis查看镜像 docker images启动容器 创建挂载目录 mkdir -p /home/local/redis/conf /home/local/redis/data创建…

【vue2项目经验总结:部署到服务器之后出现所有数据渲染失败的问题】

原因是因为在没部署到服务器之前前端为了解决跨域问题使用了代理,但是在项目部署到服务器之后,前端通常不再需要使用代理,因为代理的作用是在开发过程中帮助前端应用程序与后端服务进行通信,解决跨域访问等问题。在开发阶段&#…

分类预测 | Matlab实现DBO-CNN-SVM蜣螂算法优化卷积神经网络结合支持向量机多特征分类预测

分类预测 | Matlab实现DBO-CNN-SVM蜣螂算法优化卷积神经网络结合支持向量机多特征分类预测 目录 分类预测 | Matlab实现DBO-CNN-SVM蜣螂算法优化卷积神经网络结合支持向量机多特征分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现DBO-CNN-SVM蜣螂算法…

大厂Java面试题:MyBatis中是如何实现动态SQL的?有哪些动态SQL元素(标签)?描述下动态SQL的实现原理。

大家好,我是王有志。 今天给大家带来的是一道来自京东的 MyBatis 面试题:MyBatis 中是如何实现动态 SQL 的?有哪些动态 SQL 元素(标签)?描述下动态 SQL 的实现原理。 MyBatis 中提供了 7 个动态 SQL 语句…

国际护士节庆祝活动向媒体投稿有方法很轻松

作为一名医院职工,我肩负着医院对外信息宣传的重任。在国际护士节这个特殊的日子里,我们医院举办了一系列庆祝活动,以表彰护士们的辛勤付出和无私奉献。然而,在将这些活动信息投稿至媒体的过程中,我最初却遭遇了诸多挑战。 起初,我采用传统的邮箱投稿方式,将精心撰写的稿件发送…

基于51单片机的电子门铃设计( proteus仿真+程序+设计报告+原理图+讲解视频)

基于51单片机电子门铃设计( proteus仿真程序设计报告原理图讲解视频) 仿真图proteus7.8及以上 程序编译器:keil 4/keil 5 编程语言:C语言 设计编号:S0091 1. 主要功能: 基于51单片机的智能门铃设计 1、系统采用…

共赴科技盛会“2024南京智博会”11月在南京国际博览中心召开

2024年,南京这座历史悠久的文化名城迎来了一场科技与智慧交织的盛会——南京智博会|南京国际智慧城市、物联网、大数据。本次博览会以智慧城市、人工智能、消费电子、物联网、大数据为主题,汇聚了全球各地的智能科技精英,共同探讨智慧城市建设…

设计模式Java实现-迭代器模式

✨这里是第七人格的博客✨小七,欢迎您的到来~✨ 🍅系列专栏:设计模式🍅 ✈️本篇内容: 迭代器模式✈️ 🍱 本篇收录完整代码地址:https://gitee.com/diqirenge/design-pattern 🍱 楔子 很久…

[华为OD] B卷 树状结构查询 200

题目: 通常使用多行的节点、父节点表示一棵树,比如 西安 陕西 陕西 中国 江西 中国 中国 亚洲 泰国 亚洲 输入一个节点之后,请打印出来树中他的所有下层节点 输入描述 第一行输入行数,下面是多行数据,每行以空…

MySQL 大量数据插入优化

效率最好的方式是:批量插入 开启事务。 1、数据批量插入相比数据逐条插入的运行效率得到极大提升; ## 批量插入 INSERT INTO table (field1, field12,...) VALUES (valuea1, valuea2,...), (valueb1, valueb2,...),...;当数据逐条插入时,每…

145.二叉树的后序遍历

刷算法题: 第一遍:1.看5分钟,没思路看题解 2.通过题解改进自己的解法,并且要写每行的注释以及自己的思路。 3.思考自己做到了题解的哪一步,下次怎么才能做对(总结方法) 4.整理到自己的自媒体平台。 5.再刷重复的类…

【优选算法】——Leetcode——202—— 快乐数

目录 1.题目 2. 题⽬分析: 3.简单证明: 4. 解法(快慢指针): 算法思路: 补充知识:如何求⼀个数n每个位置上的数字的平⽅和。 总结概括 5.代码实现 1.C语言 2.C 1.题目 202. 快乐数 编写一个算法来…

Scala、Spark SQL 常用方法

目录 数组常用方法 列表操作常用方法 Scala中常用的查看列表元素的方法有head、init、last、tail和take()。 合并两个列表还可以使用concat()方法。 集合操作常用方法 map()方法 foreach()方法 filter()方法 flatten()方法 groupBy()方法 ​编辑 从内存中读取数据创建…

【Python技术】使用akshare、pandas高效复盘每日涨停板行业分析

作为一个程序员宝爸,每天的时间很宝贵,工作之余除了辅导孩子作业,就是补充睡眠。 怎么快速高效的进行当天A股涨停板的复盘,便于第二天的跟踪。这里简单写个示例, 获取当天连涨数排序,以及所属行业排序。 …

详解依赖注入的三种方法以及遇到问题的解决

各位大佬光临寒舍,希望各位能赏脸给个三连,谢谢各位大佬了!!! 目录 1.三种依赖注入的方法 1.属性注入 优点 缺点 2.构造方法注入 优点 缺点 3.Setter注入 优点 缺点 4.小结 2.依赖注入常见问题的解决 1…

人工智能中的概率魔法:解锁不确定性的智慧之钥

在人工智能(AI)的广阔天地中,概率论以其独特的魅力,成为了连接现实世界与智能决策的桥梁。从语音识别到图像识别,从自然语言处理到机器翻译,从智能推荐到自动驾驶,概率论知识在这些领域中发挥着…

ONVIF系列三:ONVIF客户端实现

ONVIF系列: ONVIF系列一:ONVIF介绍 ONVIF系列二:Ubuntu安装gSOAP、生成ONVIF代码框架 ONVIF系列三:ONVIF客户端实现 在系列二中完成了在Ubuntu上安装gSOAP并生成ONVIF代码框架,接下来我们利用生成的框架实现ONVIF客户端…