说说JVM的垃圾回收机制

news/2024/5/24 17:37:01/文章来源:https://blog.csdn.net/weixin_74783792/article/details/136662924

简介

垃圾回收机制英文为Garbage Collection, 所以我们常常称之为GC。那么为什么我们需要垃圾回收机制呢?如果大家有了解过Java虚拟机运行时区域的组成(JVM运行时存在,本地方法栈,虚拟机方法栈,程序计数器,堆,方法区五个区域),我们知道本地方法栈,虚拟机方法栈和程序计数器是线程独有的,即随着线程的存在而存在,随着线程的消亡而消亡,其内存分配和回收都具备确定性所以不用过多考虑回收问题。但是Java堆(主要存放对象)和方法区(存放常量和静态的属性,方法,对象)由于只有在运行时才知道有多少对象,所以这部分内存的分配和回收时动态的,此时就需要我们的垃圾回收机制来进行处理。

Point 1:如何判断对象是否存活

目前有两种方法来判断对象是否存活,第一种是引用计数算法,第二种是可达性分析算法。接下来我们对他们逐一进行讲解:

  • 引用计数算法:在对象中添加一个引用计数器,每当该对象被引用,计数器值就加一;引用失效就减一;当计数器为0,该对象就是不可使用的。虽然占用了一些额外空间,但是优点是效率高,但是存在一个问题,即:当两个对象互相引用时,虽然他们都不能被访问了,但是由于互相引用的原因,导致计数器为1,所以会成为漂浮垃圾,无法被回收。
  • 可达性分析算法:即在程序进行到某一点时,判断该对象能否被引用。我们可以举例结合图像来说明,本质上我们通过GC Root Set 来维护一个根集合,其中存在很多根对象节点,从这些节点向下走,经过的路径称为引用链,如若链中节点对根节点不可达,那么说明该对象不可使用了。该算法不能在应用程序活跃运行时执行对象追踪,因为执行上下文和对象图都在持续变化中,应用程序执行与可达性分析是一个竞态条件
  • 固定可做GC Roots 对象包括以下几种:
    • 在虚拟机栈中引用的对象
    • 在方法区中静态属性引用的对象
    • 在方法区中常量引用的对象
    • 在本地方法栈中JNI引用的对象
    • Java虚拟机内部的引用
    • 所有被同步锁持有的对象
    • 反射Java虚拟机内部情况的 JMXBean, JVMTI中注册的回调、本地代码缓存等

引用分类:

  1. 强引用
  2. 软引用
  3. 弱引用
  4. 幻影引用

JVM判断对象死亡过程

要真正宣布一个对象死亡需要精力两次标记过程,如若一个对象发现对于 GC Root 是不可达的,那么它将会被第一次标记。如若对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过了,那么虚拟机将这两者情况视作“没必要执行”。如若对象被确定为需要进行finalize()方法,那么该对象会被放到一个称为F-Queue的队列之中,并由一个新线程去执行finalize()方法。此时虚拟机还会对F-Queue队列中的对象进行一次小规模标记,如若对象在finalize()方法中成功自救,即连接上可达GC Root 对象的节点,即可逃脱回收。

需要注意的一点是:任何一个对象的finalize()方法都只会被系统自动调用一次,意味着如果第一次逃脱了,那么第二次如果被标记就会直接回收,不在进行finalize()方法。

Point 2:垃圾收集算法

主要有两类:“引用计数式” -- 直接垃圾收集 & “追踪式垃圾收集” -- 间接垃圾收集

分代收集

根据弱分代假说强分代假说,我们的收集器应该将Java堆划分出不同的区域,然后将回收对象依据其熬过垃圾收集过程的次数将其分配到不同区域中存储。所以目前我们的Java虚拟机一般存在新生代老年代。根据跨代引用假说我们可以得知跨代引用虽然存在,但是较于同代引用只占少数,因此我们引出了一个新的数据结构——记忆集,该数据结构用于吧老年代划分为若干小块,标记老年代哪一块内存存在跨代引用,如若新生代发生了GC的话,只用将对应小块内存加入到GC Root进行扫描就ok了。

注:新生代收集(Minor GC/ Young GC) / 老年代收集(Major GC/ Old GC) / 混合收集(Mixed GC) / 整堆收集(Full GC)

说了这么多关于收集器的分代概念,大家是否对虚拟机如何进行收集有疑问呢?接下来就来说说关于虚拟机的收集算法。主要分为三种:标记清除法,标记复制法,标记整理法。

  • 标记——清除算法:顾名思义,该算法分为两步,首先标记所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
    • 缺点:1. 执行效率不太稳定,会随着对象数量增长降低回收(标记和回收)效率。 2. 存在内存空间碎片化问题,由于每次标记和清楚都是碎片化的操作,这样就会引出一个问题,当我们需要分配一个大的对象的时候,就无法在新生代找到一个合适的区域放置这个对象,这时会触发另外一次垃圾回收动作。
  • 标记——复制算法:为了解决上一算法存在的缺点1,我们提出了该算法,又被称为半区复制算法,即将内存容量分为两个等大区域,每次只使用其中一块,如若被使用那块被用完了就将还存活的对象迁移至另一块内存空间,这个算法也避免了上述算法的缺点2。
    • 但是同样的这个算法也存在缺点:1. 大量的内存复制开销。2. 针对大多数对象都是存存活的状况,使用标记复制算法就欠妥了。3. 将可用内存空间变为内存的一半。
    • 所以针对新生代,朝生夕灭的特点,我们可以判断出标记复制算法可以用于新生代而且由于对象存活时间短数量少,我们可以将内存空间的划分做一些改变,比如按照 8:1 的比例进行分配,选取一个占比为 1/ 9 的空间作为固定的存放存货对象的空间。但是这样就会引出一个新问题,如果超过了 1/9 空间的对象存活,那么就存放不下了。这时我们就需要内存分配担保机制将新生代无法存放的存活对象直接存入老年代。
  • 标记——整理算法:由上文可知,标记复制算法的变型用于新生代回收特别方便,那么对于我们的老年代呢?根据老年代特性,我们知道其中大部分对象都是存活的,只会有少量对象需要回收。我们可以使用标记——整理的方法来解决,即标记活的对象,然后将存活对象移至一端,然后将须回收对象移至一端,我们就只清除这一端就ok了。但是这样也带来一个问题,既然老年代大多数对象都是存活的,那么每次这样移动操作对系统消耗较大而且也会导致"Stop The World" 发生。所以我们有了一种新的办法来处理老年代对象,即让虚拟机在大多数时间都采用标记——清除算法,知道内存碎片已经影响到对象分配了,我们就采用标记——整理算法对空间进行一次整理,得到一个规整的内存空间。

Point 3:HotSpot 算法实现细节

根节点枚举

由于需要查询对象引用关系,所有的收集器在根节点枚举这一步骤都是要暂停用户线程的。由于当前主流Java虚拟机采用的都是准确式垃圾收集,用户线程停顿后不需要逐个检查执行上下文,只需得到哪些地方存储着对象引用。例如HostSopt采用的就是OopMap来记录对象信息。

安全点

在OopMap的协助下,HotStop可以快速准确的完成GC Root枚举,但是导致OopMap变化的指令很多,如若每个都生成OopMap那么将会需要大量额外空间。事实上HotSopt也只在安全点进行OopMap生成,即只有到了安全点,才可以暂停用户线程进行GC。中断也有两种方式:

  1. 抢断式中断:抢断式中断不需要线程执行代码主动配合,发生GC时,会中断所有的用户线程,如果发现有用户线程中断的地方不在安全点上,就恢复这条线程知道他到达安全点再度中断。
  2. 主动式中断:不直接对线程操作,仅仅简单的设置一个标志位,每个线程都不停的轮询这个标志位,一旦发现标志位为中断就主动挂起。这个标志位和安全点是重合的。

安全区域

可以理解为加长版的安全点。如果程序在执行,它可以运行到安全点,如果程序不执行,如线程处于Sleep或者Blocked状态,这时候线程无法响应虚拟机中断请求,自然就无法走到安全点去挂起自己。所以安全区就是在某一段代码中,引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集是安全的。线程进入安全区域会标识自己,离开时会检查虚拟机是否完成了根结点枚举,如若没有收到就会被要求一直等待直到收到可以离开的信号。

Point 4:记忆集与卡表

在前文中我们提到了记忆集的概念,即用于解决查询跨代引用代价过大问题而引申出来的数据结构。

定义:记忆集是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。

有三种精度,分别是:1.字节精度,2. 对象精度, 3. 卡精度。三者中最常用的就是卡精度,顾名思义,采用卡表来实现记忆集,它定义了记忆集的记录精度与堆内存映射关系。可以联想为Java中Map。一个卡页通常包括不止一个对象,如若卡页里面有超过一个对象字段的跨代指针,那么就将对应卡表的数组元素的值设置为1,称为这个卡表变脏(dirty) 否则就是0。

卡表变脏的实际就是有其他分代区域中的对象引用了本区域的对象时,卡表就会变脏。

Point 5:写屏障

我们知道了卡表变脏时机,那么我们将如何来将卡表更新呢?由于编译过程是机器码,所以虚拟机就不方便介入,于是在我们的HotSpot中存在一个写屏障,可以看作虚拟机层面对“引用类型字段赋值”这个动作的AOP切面,在引用对象赋值时会产生一个环形通知,赋值前后都在写屏障覆盖范畴内。

一些收集器

垃圾回收器我就不过多赘述了,本质上就是对垃圾回收算法的实践。有很多版本比如 CMS, ZGC等等,如读者有兴趣可自行搜索学习。

参考资料:

《深入理解Java虚拟机》

《虚拟机设计与实现,以JVM为例》

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

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

相关文章

Uni-app跟学笔记(四):图片API、跨端兼容、组件注册、组件通信

文章目录 1)图片API1:图片上传2:图片预览 2)跨端兼容3)导航跳转4)组件及其生命周期1:注册组件2:组件生命周期 5)组件的通信1:父组件给子组件传值1-父组件2-子…

打造你的HTML5打地鼠游戏:零基础入门教程

🌟 前言 欢迎来到我的技术小宇宙!🌌 这里不仅是我记录技术点滴的后花园,也是我分享学习心得和项目经验的乐园。📚 无论你是技术小白还是资深大牛,这里总有一些内容能触动你的好奇心。🔍 &#x…

计算机组成原理实验报告1 | 实验1.1 运算器实验(键盘方式)

本文整理自博主大学本科《计算机组成原理》课程自己完成的实验报告。 —— *实验环境为学校机房实验箱。 目录 一、实验目的 二、实验内容 三、实验步骤及实验结果 Ⅰ、单片机键盘操作方式实验 1、实验连线(键盘实验) 2、实验过程 四、实验结果的…

CountDownLatch介绍和使用

1. CountDownLatch是什么 CountDownLatch 是 Java.util.concurrent 包中的一个同步工具类,用于控制线程的执行顺序。它的主要作用是让一个或多个线程等待其他线程完成操作后再继续执行。 2. CountDownLatch 类常用方法 CountDownLatch(int count) 是 CountDownLa…

String 底层是如何实现的?

1、典型回答 String 底层是基于数组实现的,并且数组使用了 final 修饰,不同版本中的数组类型也是不同的: JDK9 之前(不含JDK9) String 类是使用 char[ ](字符数组)实现的但 JDK9 之后&#xf…

安装PyTorch详细过程

安装anaconda 登录anaconda的官网下载,anaconda是一个集成的工具软件不需要我们再次下载。anaconda官网 跳转到这个页面如果你的Python版本正好是3.8版,那便可以直接根据系统去选择自己相应的下载版本就可以了。 但是如果你的Python版本号不是当前页面…

美摄科技对抗网络数字人解决方案

在数字化浪潮的推动下,企业对于高效、创新且具备高度真实感的数字化解决方案的需求日益迫切。美摄科技凭借其在人工智能和计算机视觉领域的深厚积累,推出了一款全新的对抗网络数字人解决方案,该方案能够为企业构建出表情和动作都极为逼真的数…

Unity Shader实现UI流光效果

效果: shader Shader "UI/Unlit/Flowlight" {Properties{[PerRendererData] _MainTex("Sprite Texture", 2D) "white" {}_Color("Tint", Color) (1, 1, 1, 1)[MaterialToggle] PixelSnap("Pixel snap", float…

一起玩儿3D打印机——02 3D打印机TinyBee主板、Marlin固件

摘要:本文介绍3D打印主板、固件 在前边已经介绍了3D打印机的基本组成,其中主板是3D打印机的硬件核心,而固件则是3D打印机的软件核心,在进行选择的两者一定要配合起来。因为3D打印机的核心处理器,不像PC机这样&#xff…

Visio无空白无黑边导出PDF

步骤1 文件->选项->自定义功能区->勾选开发工具 步骤2 开发工具->显示ShapeSheet->页->将Print Properties中的Margin都设置为0 步骤3 设计->大小->适应绘图 步骤4 导出PDF->选项->取消勾选【辅助功能文档结构标记】->发布

java020 - Java集合进阶

1、集合知识回顾 1.1 集合特点 提供了一种储存空间可变的储存模型,储存的数据容量随时可以发生改变。 1.2 集合类体系结构 单列集合和双列集合: 单列集合中:list和set区别(数据是否重复) 区分接口和实现类&#…

R语言lavaan结构方程模型在复杂网络分析中的科研技术新趋势

此外,我们还将深入探讨R语言的基础知识、结构方程模型的基本原理、lavaan程序包的使用方法等内容。无论是潜变量分析、复合变量分析,还是非线性/非正态/缺失数据处理、分类变量分析、分组数据处理等复杂问题,我们都将一一为您解析。 希望通过…

了解 HTTPS 中间人攻击:保护你的网络安全

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…

9、Linux-安装JDK、Tomcat和MySql

目录 一、安装JDK 1、传输JDK文件(.tar.gz) 2、解压 3、备份环境变量 4、配置环境变量 5、重新加载环境变量 6、验证(java -version) 二、安装Tomcat 1、传输文件,解压到/usr/local 2、进入Tomcat的bin目录 …

保研复习数据结构记(4)--树(二叉树、线索树、哈夫曼树,并查集)

一.树的基本术语 1.树 什么是空树?结点数为0的树非空树的特性?有且仅有一个根结点,没有后继的结点称为“叶子结点”,有后继的结点称为“分支结点”,除了根结点外任何一个结点都有且仅有一个前驱,每个结点…

Django 安装

Django 安装 在安装 Django 前,系统需要已经安装了 Python 的开发环境。 如果你还没有安装 Python,请先从 Python 官网 Welcome to Python.org 下载并安装最新版本的 Python。 Django 安装也很简单使包管理工具 pip 就可以了: pip instal…

解决ipconfig不是内部或外部命令,也不是可运行的程序或批处理文件

问题所示:ipconfig不是内部或外部命令,也不是可运行的程序或批处理文件。 解决办法如下: 1.右击此电脑,点击属性设置: 2.点击高级系统设置 3.点击进入环境变量 4.在系统变量中进行设置,双击PATH进行配置 5.点击新建&am…

第三百九十二回

文章目录 1. 概念介绍2. 方法与细节2.1 实现方法2.2 具体细节 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何混合选择多个图片和视频文件"相关的内容,本章回中将介绍如何通过相机获取图片文件.闲话休提,让我们一起Talk Flutter吧。 1. …

leetcode一天一题-第1天

为了增加自己的代码实战能力,希望通过刷leetcode的题目,不断提高自己,增加对代码的理解,同时开拓自己的思维方面。 题目名称:两数之和 题目编号:1 题目介绍: 给定一个整数数组 nums 和一个整数…

AHU 汇编 实验一

一、实验名称:实验1 实验1 用Debug命令查看寄存器和内存中的内容 实验目的:求掌握使用Debug命令查看寄存器和内存的方法。 通过第2章两个简单实例认识汇编语言程序,初步了解程序格式;段定义;标号;DOS系统功能&#xf…