JVM—垃圾收集算法和HotSpot算法实现细节

 参考资料:深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)周志明

1、分代回收策略

  • 分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。

  • 分代垃圾回收采用分治的思想,进行代的划分,把不同生命周期放在不同代上,不同代采用最适合它的垃圾回收方法进行回收。

1.1 代际划分

1.2 垃圾回收

1.2.1 年轻代(新生代)

年轻代会划分出Eden区域与两个大小对等的Survivor区域。其比例一般为8:1:1,这是因为根据统计95%的对象朝生夕死,存活时间极短。

  • 当新对象生成,并且在Eden申请空间失败时,就会触发GC,清理Eden中的非存活对象,并且把存活的对象移动到Survivor区,接下来整理两个Survivor区域。

  • 这种方式不会影响到年老代,因为大部分对象都是从Eden区开始的,所以Eden区垃圾回收十分频繁,需要快速、高效的算法(一般采用复制算法)。

1.2.2 年轻代收集(Minor GC/Young GC)

  1. 当新对象在Eden区域内存分配失败时就会触发年轻代的垃圾回收,称之为“Minor GC”

  2. 每个对象都有一个年龄,这个年龄就是指对象经历过Minor GC的次数。

如图一所示,对象刚分配到Eden时年龄为“0”,当Minor GC被触发时,所有存活的对象会被拷贝到其中一个Survivor区域中,同时年龄增长“1”,最后清除Eden区域非可达对象

如图二所示,当第二次Minor GC被触发时、JVM会通过Mark算法 (标记算法)找出Eden区域和Survivor1区域存活的对象并将他们拷贝到新的Survivor2区域,同时年龄增长加“1”,最后清除Eden区域和Survivor1区域非可达对象。

如图三所示,当对象年龄足够大(年龄通过JVM参数设置),并且Minor GC再次发生,他就会从Survivor内存区域升级到年老代中。

1.2.3 年老代

  • 年老代是用来存放长时间存活的对象的内存区域。

  • 当对象在新生代中经历了多次垃圾回收仍然存活时,它们会被晋升到年老代。

  • 年老代的垃圾回收频率较低,并且每次回收的成本较高

在年老代选择的垃圾回收算法取决于JVM采用的什么垃圾回收器

1.2.4 年老代收集(Major GC/Old GC)

  1. Minor GC发生时,又有对象从Survivor区域升级到Tenured区域,但是Tenured区域没有空间容纳新的对象,此时就会触发年老代的垃圾回收。

  2. 年老代的垃圾回收算法取决于JVM选择的垃圾回收器。

2、 垃圾收集算法(Mark)

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

过程:

算法分为标记、清除两个过程。

  1. 首先标记出所有需要回收的对象/标记存活的对象。

  2. 接下来清除未被标记的对象。

标记清除算法是最基础的算法,后续收集算法基本上都是以标记-清除算法为基础。

标记-清除算法的主要缺点有两个:

  1. 执行效率不稳定,如果 Java堆中有大量对象并且其中大部分需要回收,这时必须进行大量的标记清除动作导致两个过程执行效率随对象数量增加而增大。

  2. 空间碎片化严重,会产生大量不连续的空间碎片,碎片太多分配较大对象会导致没有足够的连续内存从而发生垃圾回收。

标记-清除算法执行过程如图所示:

2.2 标记-复制算法(Mark-Copy)

为了解决标记-清除算法面对大量可回收对象时执行效率低的问题,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。

过程:

  1. 当其中一块内存用完时,将存活的对象复制到另一块上面。

  2. 然后清除使用过的这块内存。

标记-复制算法的缺点:

  1. 如果内存大多对象都是存活的,这种算法会产生大量的内存复制间的开销。

  2. 复制算法的代价就是将可用内存缩小了原来的一半,空间浪费较大。

标记-复制算法的执行过程:

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

针对于年老代对象的存亡特征,提出了标记-整理算法,其标记过程和“标记-清除”算法一样,但是后续不是清除可回收对象而是让所有存活的对象都向内存空间一端移动

  • 标记-清除算法和标记-整理算法本质差异在于前者是一种非移动式的回收算法,而后者是移动的。

  • 优点:这种方式避免碎片内存的产生,又不需要两块相同的内存

  • 缺点:压缩操作需要进行局部对象移动

3、HotSpot算法的实现细节

3.1 根节点枚举

所谓“一致性”就是整个枚举期间不会出现枚举过程中,整个根节点集合的对象引用在不断发生变化。

  • 主流Java虚拟机都是使用的准确式垃圾收集。当用户线程停下时,不需要一个不漏的检查所有执行上下文和全局引用位置。

在HotSpot的解决方案中:

  1. 使用一组称为OopMap的数据结构来达到目的。

  2. 一旦类加载完成,HotSpot就会将对象内什么偏移量上是什么类型的数据计算出来。

  3. 在即时编译中也会在特定位置安全点)记录栈和寄存器里哪些位置是引用。

这样收集器扫描就可以直接得到这些信息,而不需要一个不漏的从方法区等待GC Roots查找。

3.2 安全点

  • 导致OopMap内容变化的指令非常多,如果为每个指令生成对应的OopMap将需要大量的额外存储空间。

  • 安全点决定了用户执行时不能在代码指令流的任意位置停下来进行GC,必须强制到达安全点才能执行GC。

  • 安全点一般选取在“是否具有长时间执行特征”的地方,例如方法调用、循环跳转、异常跳转。

如何让垃圾收集发生时所有线程都跑到最近的安全点,然后停顿?

  1. 抢先式中断:在GC发生时,系统把用户线程全部中断,如果发现有中断的线程不在安全点上,就恢复这条线程执行,让它直到跑到安全点上中断。(现在几乎没有虚拟机采用这种方式)

  2. 主动式中断:需要中断线程时,不直接操作线程而是简单的设置一个标志位,各个线程执行时会不停的主动询问这个标志。一旦这个标志位为真就在自己最近的安全点上主动中断挂起。

3.3 安全区域

安全点似乎已经完美解决了停顿用户线程,让虚拟机进行垃圾回收状态。但是当程序“不执行”时,安全点就没有作用。

典型场景:用户线程处于Sleep状态/Blocked状态,这时线程无法响应虚拟机中断请求,不能走到安全的地方去中断挂起自己。

  • 安全区是指确保在某一代码片段中,引用关系不会发生变化。因此这个区域任意地方发生垃圾回收都是安全的。

  • 当用户线程执行安全区的代码,首先会标记自己已经进入了安全区域,这段时间虚拟机发起GC就不会管已声明自己在安全区的线程。

  • 当线程需要离开安全区时,它会先检查虚拟机是否完成根节点枚举(或者GC过程中其他需要暂停用户线程的阶段)。如果完成那么线程就会继续执行,否则就会一直等待,直到收到离开安全区域的信号为止。

3.4 记忆集与卡表

在分代收集理论中,为了解决对象跨代引用所带来的问题,垃圾收集器在新生代中建立了名为记忆集的数据结构。

记忆集是一种用于记录从非收集区指向收集区域指针集合的抽象数据结构,它用于缩减GC Roots扫描范围。

在垃圾收集的场景中,收集器只需要通过记忆集判断出某一块非收集区域是否存在指向收集区域的指针就行了,并不需要了解这些跨代指针的全部细节。

设计实现记忆集时,可以选择不同的记录粒度来节省记忆集的存储和维护成本:

第三种卡精度是用一种“卡表”的方式去实现记忆集,也是目前最常用实现记忆集的形式。 HotSpot虚拟机卡表的实现:

  1. 卡表最简单的形式可以是一个字节数组。

  1. 字节数组CARD_TABLE的每个元素都对应着其标识的内存区域中一块特定大小的内存块。

  2. 一个卡页中通常存在不止一个对象,只要卡页有一个对象存在跨代指针,就将对应的卡表数组元素标记为1,称“元素变脏”。

  3. 在垃圾收集时只需要,筛选卡表中变脏的元素,就能得到哪些卡页内存包含跨域指针,把它们加入GC Roots中扫描。

3.5 写屏障

我们使用记忆集缩减GC Roots扫描的范围问题,但是还没解决卡表元素如何维护,什么时候变脏,谁来让它变脏?

  • 什么时候变脏?——有其他分代区域的对象引用了本区域对象时,对应卡表元素就应该变脏。

HotSpot通过写屏障来维护卡表状态,帮助虚拟机跟踪对象引用的变化。

  • 应用写屏障后,虚拟机就会为赋值操作生成相应指令,一旦收集器在写屏障增加了更新卡表的操作,无论更新是不是年老代对新生代对象的引用,每次对引用更新就会产生额外开销。但是这个开销远小于扫描这个年老代的开销。

  • 卡表在高并发下还存在“伪共享”的问题。

  1. 现代CPU缓存系统是以缓存行为单位存储的

  2. 多线程下修改互相独立的变量时,这些变量如果共享同一个缓存行,就会彼此影响导致性能降低。

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

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

相关文章

python实现小游戏——植物大战僵尸(魔改版本)

制作一款DIY的‘植物大战僵尸’游戏引起了很多人的兴趣。在这里,我将分享一个使用Python语言在PyCharm环境中开发的初始状态版本。这个版本主要应用了pygame库来完成,是一个充满创意和趣味的魔改版本。 文章目录 前言一、开发环境准备二、代码1.main方法…

Linux小组件:gcc

gcc 是C语言的编译器,在Linux下我们也用这个编译C语言 安装gcc sudo apt install build-essential 查看gcc版本信息 gcc --version 有时候会出现代码编译不过去的问题,通常可能是gcc的编译标准太低,不支持某些写法 比如在很多旧的编译标…

SQL注入实例(sqli-labs/less-4)

0、初始页面 1、确定闭合符号 前两条判断是否为数值型注入,后两条判断字符型注入的闭合符号 ?id1 and 11 ?id1 and 12 ?id1" ?id1") 2、确定表的列数 ?id1") order by 3 -- 3、确定回显位置 ?id-1") union select 1,2,3 -- 4、爆库…

【kali靶机之serial】--反序列化漏洞实操

kali靶机配置 【我图片里没有截图的默认配置即可】需要改的地方图片里面都有。 使用kali扫描网关的主机。 扫到一个开放了80端口HTTP协议的主机ip 访问80端口 会看到一个文本页面,翻译一下看是什么意思。。 F12查看cookie,是一个base64编码了的东西 使…

新手小白学习PCB设计,立创EDA专业版

本教程有b站某UP主的视频观后感 视频链接:http://【【教程】零基础入门PCB设计-国一学长带你学立创EDA专业版 全程保姆级教学 中文字幕(持续更新中)】https://www.bilibili.com/video/BV1At421h7Ui?vd_sourcefedb10d2d09f5750366f83c1e0d4a…

指标一致化处理

什么是数据指标 数据指标有别于传统意义上的统计指标,它是通过对数据进行分析得到的一个汇总结果,是将业务单元精分和量化后的度量值,使得业务目标可描述、可度量、可拆解。 数据指标有哪些类型 极大型:期望取值越大越好; 极小…

Memcached未授权访问漏洞

Memcached 是一套常用的 key-value 分布式高速缓存系统,由于 Memcached 的安全设计缺陷没有权限控制模块,所以对公网开放的Memcache服务很容易被攻击者扫描发现,攻击者无需认证通过命令交互可直接读取 Memcached中的敏感信息。 app"Mem…

AI电销机器人的效果与作用

ai电销机器人的工作效率是非常高的,电销机器人一天的外呼量至少是3000左右,工作效率是人工的十倍还多,并且电销机器人没有负面情绪,一直都可以保持高昂的工作热情,非常简单方便。 并且ai电销机器人是越用越聪明的&…

英国AI大学排名

计算机学科英国Top10 “计算机科学与信息系统”学科除了最受关注的“计算机科学”专业,还包括了“人工智能”“软件工程”“计算机金融”等众多分支专业。 1.帝国理工学院 Imperial College London 单以计算机专业本科来讲,仅Computing这个专业&#x…

来点八股文(五) 分布式和一致性

Raft raft 会进入脑裂状态吗?描述下场景,怎么解决? 不会。raft通过选举安全性解决了这个问题: 一个任期内,follower 只会投票一次票,且先来先得;Candidate 存储的日志至少要和 follower 一样新…

用uniapp 及socket.io做一个简单聊天app 4

界面如下&#xff1a; <template><view class"container"><input v-model"username" placeholder"用户名" /><input v-model"password" type"password" placeholder"密码" /><butto…

comfyUI-MuseTalk的参数设置

comfyUI-MuseTalk的参数设置 目录 comfyUI-MuseTalk的参数设置 一、ComfyUI-VideoHelperSuite 二、comfyUI-MuseV合成的参考视频 2.1、什么时候会用到MuseV&#xff1f; 2.2、MuseV特别消耗系统内存 2.2.1、测试图片序列的像素比 2.2.2、影响运动范围和生成结果的参数 …

Yarn:一个快速、可靠且安全的JavaScript包管理工具

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;还请三连支持一波哇ヾ(&#xff20;^∇^&#xff20;)ノ&#xff09; 目录 一、Yarn简介 二、Yarn的安装 1. 使用npm安装Yarn 2. 在macOS上…

基于Springboot的个人博客系统

文章目录 介绍访问地址一、功能展示1.前台首页归档相册留言关于我登陆注册 2.后台管理系统登陆页面首页文章管理相册管理写博客访客统计 介绍 基于Java&#xff08;Springboot&#xff09;可以用做毕业设计的个人博客系统&#xff0c;包括网站前台和后台管理系统两部分。网站前…

C++中const关键字的用法

C语言和C中const的不同 首先我们需要区分一下C语言中的const和C中的const&#xff0c;C语言中的const修饰的变量可以不初始化&#xff0c;但如果将一个变量定位为const类型还不初始化&#xff0c;那么之后就不能对这个变量直接赋值了。 如果我们使用C语言中的const定义的变量指…

c++ 21 指针

*像一把钥匙 通过钥匙去找内存空间 间接修改内存空间的值 不停的给指针赋值 等于不停的更改指针的指向 指针也是一种数据类型 指针做函数参数怎么看都不可以 指针也是一个数据类型 是指它指向空间的数据类习惯 作业 野指针 向null空间地址copy数据 不断改变指针指向 …

将本地微服务发布到docker镜像二:

上一篇文章我们介绍了如何将一个简单的springboot服务发布到docker镜像中&#xff0c;这一篇我们将介绍如何将一个复杂的微服务&#xff08;关联mysql、redis&#xff09;发布到docker镜像。 我们将使用以下两种不同的方式来实现此功能。 redis、mysql、springboot微服务分开…

Redis未授权访问漏洞 *

#安装redis apt-get install redis #redis链接 redis-cli -h 192.168.4.176 -p 6379 #redis常见命令 &#xff08;1&#xff09;查看信息&#xff1a;info &#xff08;2&#xff09;删除所有数据库内容&#xff1a;flushall &#xff08;3&#xff09;刷新数据库&#xff1a;f…

平衡二叉树 - 力扣(LeetCode) C语言

110. 平衡二叉树 - 力扣&#xff08;LeetCode&#xff09;&#xff08;点击前面链接即可查看题目&#xff09; 一、题目 给定一个二叉树&#xff0c;判断它是否是 平衡二叉树 是指该树所有节点的左右子树的深度相差不超过 1。 示例 1&#xff1a; 输入&#xff1a;root […

unity拖拽物品遇到的bug及解决思路

记录一下拖拽实现过程中遇到的bug RectTransform 专门用在UI中transform 判断点击是否在UI中 使用这个函数就可以判断点击的是否是UI面板&#xff0c;返回true表明在UI面板中 EventSystem.current.IsPointerOverGameObject()值得一提的是&#xff0c;如果发现了有UI穿透效…