深入理解Java虚拟机——垃圾回收算法
1.前言
垃圾回收需要完成的三件事
首先我们需要明白垃圾回收需要完成的三件事:
- 哪些内存需要回收
- 堆内存中的对象所使用的内存
- 方法区中的废弃的常量以及不再使用的类型
- 什么时候回收
- 当对象死亡
- 方法区中某些内容(常量和类型)不再被使用
- 如何回收
明白了这三个步骤,对你后面的阅读,逻辑会更加清晰。
为什么要了解垃圾回收和内存分配?
当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就必须对这些自动化的技术实施必要的监控和调节。
2.如何判断对象已死?
所谓死去,就意味着该对象不能再被任何途径使用。
2.1 引用计数算法
基本思路
引用计数法的基本思路是在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当有一个引用失效时,计数器值就减1;当任何时候,计数器的值为0,就意味着该对象不可能再被使用。
存在的问题
客观来说,引用计数法使用了极少空间进行计数,判定效率很高。
但是它存在很多难以解决的问题,举一个显而易见的例子:循环依赖就是一个很棘手的情况。
这里假设虚拟机使用引用计数算法判断对象是否已经死亡。比如说Java虚拟机的堆内存中,存在对象A和对象B,没有任何对象引用对象A和对象B,但是A引用了B,B也引用了A,这就造成了循环依赖,即使我们将A和B手动赋值为Null,但是实际上循环依赖仍然存在,这就导致了其无法被GC回收。
虽然引用计数算法简单高效,但是需要配合大量的额外处理才能保证正常工作,所以主流的Java虚拟机都没有采用该算法来管理内存。
2.2 可达性分析算法
基本思路
可达性分析算法的基本思路是通过一系列称为GC Roots的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径被称为引用链,如果某个对象到GC Roots间没有任何引用链相连,则证明该对象是不能再被使用的。
在这张图中,绿色的就是存活的对象,灰色的不与GC Roots相连,意味着不可达,也就是会被判断为可回收的对象。
哪些对象可以作为GC Roots的对象?
根据是否固定,我们可以将可以作为GC Roots的对象分为:
- 固定作为GC Roots的对象
- 不固定作为GC Roots的对象
固定可作为GC Roots的对象
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
- 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
- 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
- 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
- Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
- 所有被同步锁(
synchronized
关键字)持有的对象。 - 反映Java虚拟机内部情况的JMXBean、JVM TI中注册的回调、本地代码缓存等。
不固定作为GC Roots的对象
除了固定GC Roots集合以外,根据对象所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象临时性的加入,共同构成GC Roots的集合。
譬如后文将会提到的分代收集和局部回收(Partial GC),如果只针对Java堆中某一块区域发起垃圾收集时(如最典型的只针对新生代的垃圾收集),必须考虑到内存区域是虚拟机自己的实现细节(在用户视角里任何内存区域都是不 可见的),更不是孤立封闭的,所以某个区域里的对象完全有可能被位于堆中其他区域的对象所引用(跨代引用),这时候就需要将这些关联区域的对象也一并加入GC Roots集合中去,才能保证可达性分析的正确性。
HotSpot虚拟机:记忆集与卡表
打个比方来说如果是只针对新生代的垃圾收集,存在跨代引用,为了保证可达性分析的正确性,需要将关联区域的对象一并加入GC Roots集合中去。
为了解决对象跨代引用所带来的问题,垃圾收集器在新生代中建立了名为记忆集的数据结构,用于避免把整个老年代加进GC Roots的扫描范围。记忆集是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。
对于记忆集的实现有三种常见的方式:
- 字长精度:每个记录精确到一个机器字长(就是处理器的寻址位数,如常见的32位或64位,这个精度决定了机器访问物理内存地址的指针长度),该字包含跨代指针。
- 对象精度:每个记录精确到一个对象,该对象里有字段含有跨代指针。
- 卡精度:每个记录精确到一块内存区域,该区域内有对象含有跨代指针。
第三种卡精度所指的是用一种称为卡表(Card Table)的方式去实现记忆集,这也是目前最常用的一种记忆集实现形式。卡表就是记忆集的一种具体实现,它定义了记忆集的记录精度、与堆内存的映射关系等。
在HotSpot虚拟机中,使用卡表实现记忆集,对于卡表的实现仅仅使用了一个字节数组。
字节数组种的每一个元素都对应着其标识内存区域种一块特定大小的内存块,这个内存块被称为卡页。
一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字段存在着跨代指针,那就将对应卡表的数组元素的值标识为1,称为这个元素变脏(Dirty),没有则标识为0。在垃圾收集发生时,只要筛选出卡表中变脏的元素,就能轻易得出哪些卡页内存块中包含跨代指针,把它 们加入GC Roots中一并扫描。
2.3 finalize()方法
宣告一个对象死亡,至少需要进行两次标记过程。
- 如果对象进行可达性分析后发现没有与GC Roots相连接的引用链,那么它将会被第一次标记,随后进行一次筛选,筛选条件是此对象是否有必要执行
finalize
方法。- 假如对象没有覆盖
finalize
方法或者finalize
方法已经执行过,则被视为没有必要执行。在第二次标记时会被列入“即将回收”集合。 - 假如对象覆盖了
finalize
方法,则会将该对象放入一个名为F-Queue
的队列中,并且稍后由一条虚拟机自动建立的低调度优先级的Finalizer
线程去执行他们的finalize
方法。如果在该对象的finalize
方法中成功将该对象与引用链上任意一个对象建立关联,那么第二次标记就会将它移出“即将回收”集合。
- 假如对象没有覆盖
2.4 回收方法区
方法区的垃圾收集主要回收两部分的内容:废弃的常量和不再使用的类型。
判断常量是否废弃
如果有一个字符串已经进入了方法区中的常量池中,但是已经没有任何对象引用这个字符串变量,如果这个时候发生垃圾回收,并且垃圾收集器判断确实有必要就会将该字符串清出字符串常量池中。
判断类是否废弃
相对于判断常量是否废弃,类的废弃判断要复杂一些。判断一个类为“不再使用的类”需要同时满足三个条件:
- 该类的所有实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例
- 加载该类的类加载器已经被回收,这个条件除非是精心设计的可替换类加载器的场景(JSP的重加载),否则一般很难达成
- 该类对应的Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
即使一个类被判断为“不再使用的类”,也需要Java虚拟机允许对其进行回收。并不像对象一样,没有引用了自然就会回收。
3. 垃圾收集算法
分代收集理论
当前商业虚拟机的垃圾收集器大多数都遵循了“分代收集”的理论进行设计,分代收集建立在两个分代假说上:
- (1)弱分代假说:绝大多数对象都是朝生夕灭的
- (2)强分代假说:熬过越多次垃圾收集过程的对象就越是难以消亡
这两个分代假说共同奠定了多款常用的垃圾收集器的一致的设计原则:收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。
在将Java堆内存划分为不同的区域之后,垃圾收集器才可以每次只回收其中某一个或者某些部分的区域(Minor GC、Major GC、Full GC)。进而演化出与对象存亡特征相匹配的垃圾收集算法。
垃圾收集行为根据区域具体可以如下划分:
- Partical GC:不完整收集整个Java堆的垃圾收集
- Minor GC:指目标只是新生代的垃圾收集
- Major GC:指目标只是老年代的垃圾收集(目前只有CMS收集器会有单独收集老年代的行为)
- Mixed GC :指目标是收集整个新生代和老年代的垃圾收集(目前只有G1收集器有这种行为)
- Full GC:收集整个Java堆和方法区的垃圾收集
除了前两个分代假说外,还存在第三条假说:
- (3)跨代引用假说:跨代引用相对于同代引用来说仅占极少数。
该假说的隐含之意就是:存在互相引用关系的两个对象,是应该倾向同时生存或者同时消亡的。
这也就意味着我们不必为了少量的跨代引用区扫描整个老年代,也不必专门浪费空间记录每一个对象是否存在及存在哪些跨代引用。我们只需要在新生代建立一个全局数据结构,该结构将老年代划分为若干块,标识出哪一块内存会存在跨代引用。发生MinorGC的时候,只有被标识的”老年代块“才会被加入到GC Roots进行扫描。
3.1 标记-清除算法
标记清除算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,标记完成后,统一回收掉所有被标记的对象。
标记清除算法主要存在两个缺点:
- 执行效率不稳定
- 容易导致大量内存空间碎片
3.2 标记-复制算法
标记复制算法常被称为复制算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。该算法适用于大多数对象都是可回收的情况,如果大部分对象都存活,则意味着要进行大量的复制操作(将存活的对象赋值到另一块内存中),会带来额外开销。
标记复制算法的优点在于实现简单,运行高效,但是同样缺点也很明显,这样会缩小可用内存空间。
但事实上,根据研究98%的对象都活不过第一轮收集,所以并不需要完全按照1:1
分配内存。HotSpot虚拟机中的新生代垃圾收集器均采用了Appel式回收来设计新生代内存布局。
Appel式回收的具体做法是把新生代分为一块较大的Eden空间和两块较小的 Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1,也即每次新生代中可用内存空间为整个新 生代容量的90%(Eden的80%加上一个Survivor的10%),只有一个Survivor空间,即10%的新生代是会 被“浪费”的。当然,98%的对象可被回收仅仅是“普通场景”下测得的数据,任何人都没有办法百分百 保证每次回收都只有不多于10%的对象存活,因此Appel式回收还有一个充当罕见情况的“逃生门”的安 全设计,当Survivor空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域(实际上大多就是老年代)进行分配担保(Handle Promotion)。
所谓分配担保就是:如果另外一块 Survivor空间没有足够空间存放上一次新生代收集下来的存活对象,这些对象便将通过分配担保机制直 接进入老年代,这对虚拟机来说就是安全的。
3.3 标记-整理算法
标记整理算法的标记过程同标记清除算法一样。而后续步骤不是直接对可回收对象进行回收,而是让所有存活对象向一段移动,然后直接清理掉边界以外的内存。
标记整理算法相对于标记清除算法的本质差异在于,前者是一种移动式的回收算法,后者是非移动式的。
是否移动回收后的存活对象是一项优缺点并存的风险决策:
- 如果移动存活对象,意味着全程必须暂停用户应用程序才能进行,也就是发生
STOP THE WORLD
- 如果不移动存活对象,空间碎片化问题就只能依赖更为复杂的内存分配器和内存访问器来解决
整理算法相对于标记清除算法的本质差异在于,前者是一种移动式的回收算法,后者是非移动式的。
相关文章:

深入理解Java虚拟机——垃圾回收算法
1.前言 垃圾回收需要完成的三件事 首先我们需要明白垃圾回收需要完成的三件事: 哪些内存需要回收 堆内存中的对象所使用的内存方法区中的废弃的常量以及不再使用的类型 什么时候回收 当对象死亡方法区中某些内容(常量和类型)不再被使用 如…...

git-rebase和merge
A-----B----C----D master E----F-----G feature 为了把main分支里新增的代码应用在你的feature分支,你有两种方法:merge 和 rebase。 merge git checkout feature git merge main A-----B----C----D master E----F-----G -----* feature (合并master…...

【JavaWeb 用户认证】Cookie、Session、Token、JWT、Interceptor、SpringBoot、Spring Security
Token基本了解:【详细阐述Token的来源】公钥私钥基本了解:【理解公钥】 文章目录 一、Cookie 经典介绍以及使用案例二、Session 经典介绍以及拦截登录案例三、Token MySQL 的基本介绍及其基本使用四、JWT 基本介绍及其基本讲解五、SpringBoot 使用拦截器…...

6个月的测试,来面试居然要15K,我一问连5K都不值
2023年4月份我入职了深圳某家创业公司,刚入职还是很兴奋的,到公司一看我傻了,公司除了我一个自动化测试,公司的测试人员就只有2个开发3个前端1个测试还有2个UI,在粗略了解公司的业务后才发现是一个从零开始的项目&…...

RSA--维纳攻击--代码和题目分析
文章目录 维纳攻击原理:维纳攻击脚本[羊城杯 2020]RRRRRRRSA 1题目描述:题目分析: 收获与体会: 维纳攻击原理: 两位大佬讲得非常清楚(搬运工就是我):https://zhuanlan.zhihu.com/p/…...

飞腾ft2000-麒麟V10-SP1安装Docker、运行gitlab容器
目录 一、安装及配置docker 1、卸载docker相关包及删除相关配置文件 2、安装二进制docker 1.下载软件包 2.解压 3.修改镜像加速地址 4.修改profile文件 5.启动docker 6.docker常用命令 二、安装并启动gitlab镜像 1.安装gitlab镜像 1.查询满足使用需求的gitlab版本 2…...

C++ 的类型转换
目录 1. C语言中的类型转换 2. C强制类型转换 2.1static_cast 2.2 reinterpret_cast 2.3 const_cast 2.4 dynamic_cast 3. RTTI(了解) 1. C语言中的类型转换 在 C 语言中,如果 赋值运算符左右两侧类型不同,或者形参与实参类型不…...

【Windows】普通控制台EXE程序转为windows服务方式运行的详细步骤
背景 NSSM(Non-Sucking Service Manager)是一个免费的第三方Windows服务管理器,可以将任何可执行文件转换为Windows服务。官网下载地址为:https://nssm.cc/download 以下是NSSM配置Windows服务的详细步骤和注意事项: …...

NSSCTF [suctf 2019]hardcpp WP 控制流混淆
下载文件,64位主函数非常多循环 去控制流混淆,脚本下载deflat 用法 python 脚本名 文件名 起始地址例如主函数地址是0x4007E0 python deflat.py hardCpp 0x4007E0然后就生成了去混淆的文件 主函数非常大,开始分析逻辑 puts("func(?…...

计算机毕业论文内容参考|基于神经网络的网络安全态势感知技术研究
文章目录 导文文章重点摘要前言绪论课题背景国内外现状与趋势课题内容相关技术与方法介绍技术分析技术设计技术实现总结与展望导文 基于神经网络的网络安全态势感知技术研究 文章重点 摘要 随着互联网的快速发展,网络攻击的频率和复杂度也在逐年增加。为了更好地保护信息系统…...

Flask框架之Request、Response、Cookies、Session等对象的使用
Request、Response、Cookies、Session等对象的使用 Request对象基本使用参数的获取转换器内置转换器自定义转换器 Response对象基本使用返回模板重定向返回JSON Cookies对象设置cookie获取cookie删除cookie Session会话对象设置SECRET_KEY设置会话获取会话释放会话 Request对象…...

信号与槽机制一
一、信号与槽 1、什么是信号与槽? 信号和槽是用于对象之间的通信,它是Qt的核心机制,在Qt编程中有着广泛的应用。如果想学好Qt,一定要充分掌握信号的槽的概念与使用。 2、信号和槽的代码实例 在Qt中,发送对象、发送的信…...

nodejs 复制文件到指定目录
var fs require(fs), path require(path), exec require(child_process).exec, sourcePath, targetPath; //获取命令行中的路径 process.argv.forEach(function (val, index, array) { if (index 2) { sourcePath val; } if (index 3) { targetPath val; } }); // 定义…...

第八章 使用Apache服务部署静态网站
文章目录 第八章 使用Apache服务部署静态网站一、网站服务程序1、网站服务介绍2、Apache程序介绍 二、配置服务文件参数1、Linux系统中的配置文件2、配置httpd服务程序时最常用的参数以及用途描述 三、SELinux安全子系统1、SELinux介绍2、SELinux服务配置模式3、Semanage命令4、…...

Three——四、几何体、高光网络材质、锯齿模糊以及GUI库的使用
文章: Three——一、初识Three以及基础的前端场景搭建(结尾含源码)Three——二、加强对三维空间的认识Three——三、动画执行、画布大小、渲染帧率和相机适配体验Three——四、几何体、高光网络材质、锯齿模糊以及GUI库的使用Three——五、点线模型对象、三角形概念…...

盲目自学网络安全只会成为脚本小子?
前言:我们来看看怎么学才不会成为脚本小子 目录: 一,怎么入门? 1、Web 安全相关概念(2 周)2、熟悉渗透相关工具(3 周)3、渗透实战操作(5 周)4、关注安全圈动…...

文从字顺|程序员须知,如何编写高质量代码
高质量代码是软件开发中至关重要的一部分。高质量的代码不仅可以提高软件的可维护性和可复用性,还可以增强软件的安全性和稳定性。同时,可以降低软件维护成本,提升开发效率,为用户提供更好的使用体验。 写出高质量代码是每个程序…...

PCIe物理层弹性缓存机制(详细)解析-PCIe专题知识(四)
目录 前言一、简介二、详细解析2.1 实例解析2.2 具体实现过程 三、总结四、其他相关链接1、PCI总线及发展历程总结2、PCIe物理层总结-PCIE专题知识(一)3、PCIe数据链路层图文总结-PCIe专题知识(二)4、PCIe物理层链路训练和初始化总…...

分片上传和断点续传的区别?实现思路是什么?
相同: 分片上传和断点续传都是网络传输中常用的重要技术 不同: 分片上传:将一个大文件切分为多个小文件进行上传。这种方式能够加快上传速度,降低服务器压力,特别适用于大型文件的上传。例如,在云存储系统…...

微前端 qiankun@2.10.5 源码分析(二)
微前端 qiankun2.10.5 源码分析(二) 我们继续上一节的内容。 loadApp 方法 找到 src/loader.ts 文件的第 244 行: export async function loadApp<T extends ObjectType>(app: LoadableApp<T>,configuration: FrameworkConfi…...

08异步请求:何种场景下应该使用异步请求?
异步在计算机科学中早就是一个比较常用的词汇,从操作系统的特性( 并发、共享、虚拟、异步)开始,异步就在处理并发操作中起到很大的作用,不仅如此,在软件层面,异步同样也是解决并发请求的一个关键过程,它可以将瞬时激增的请求进行更加灵活的处理,通过异步请求,客户端可…...

【深度学习 | Transformer】Transformers 教程:pipeline一键预测
文章目录 一、前言二、Computer vision2.1 Image classification2.2 Object detection2.3 Image segmentation2.4 Depth estimation 三、NLP3.1 Text classification3.2 Token classification3.3 Question answering3.4 Summarization3.5 Translation3.6 Language modeling3.6.…...

HTMLCSS
1、HTML 1.1 介绍 HTML 是一门语言,所有的网页都是用HTML 这门语言编写出来的,也就是HTML是用来写网页的,像京东,12306等网站有很多网页。 这些都是网页展示出来的效果。而HTML也有专业的解释 HTML(HyperText Markup Language)…...

【安装Nginx】
Linux上安装Nginx 文章目录 Linux上安装NginxUbuntuCentOS查看已安装的软件 Ubuntu 在 Ubuntu 上安装 Nginx 非常简单。只需按照以下步骤操作: 打开终端,更新软件包索引: sudo apt update安装 Nginx: sudo apt install nginx安…...

VSCode作业1:猜数字游戏和简单计数器(包含完整代码)
目录 猜数字游戏 一、使用‘random’函数获取随机数 二、 分情况讨论输入值大小情况 三、HTML代码 四、CSS样式及运行效果 简单计数器(计时器) 一、使用‘setInterval’函数实现计数效果 二、使用’clearInterval‘函数实现暂停计数和重新计数效果 …...

NANK OE骨传导开放式蓝牙耳机发布,极致体验拉满!
近日,中国专业音频品牌NANK南卡发布了全新一代——骨传导开放式蓝牙耳机NANK OE,耳机采用了传统真无线和骨传导的结合方式,带来更加舒适的佩戴体验和音质升级,同时还支持单双耳自由切换,全新的设计收获了市场的喜爱和认…...

看完这篇文章你就彻底懂啦{保姆级讲解}-----(I.MX6U驱动GPIO中断《包括时钟讲解》) 2023.5.9
目录 前言整体文件结构源码分析(保姆级讲解)中断初始化部分初始化GIC控制器初始化中断向量表设置中断向量表偏移 系统时钟初始化部分使能所有的时钟部分led初始化部分beep初始化部分key初始化部分按键中断初始化部分按键中断服务函数部分 while循环部分 …...

MySql -- 事务
目录 1.概念 2.事务的运用场景 3.事务的四大特点 4.执行事务带来的问题 4.1 脏读 4.2 不可重复度 4.3 幻读 5. MySQL中事务的隔离级别 1.概念 事务就是把若干个独立操作打包成一个整体而诞生的一种功能. 2.事务的运用场景 比如:A——>B 转账500 A的余额-500…...

关于大模型对未来影响的一点看法
人们总是高估了未来一到两年的变化,低估了未来十年的变革。 ---比尔盖茨 近来OpenAI的GPT技术可以说在全球都带来了巨大的影响,也让大家看到了什么叫大力出奇迹。chatGPT和GPT4的能力给了大家很大的震撼,其流畅自如、逻辑清晰、出众的能力&am…...

Android - 约束布局 ConstraintLayout
一、概念 解决布局嵌套过多的问题,采用方向约束的方式对控件进行定位。 二、位置约束 2.1 位置 至少要保证水平和垂直方向都至少有一个约束才能确定控件的位置。 layout_constraintLeft_toLeftOf我的左边,与XXX左边对齐。layout_constraintLeft_toRight…...