当前位置: 首页 > news >正文

深入探究 JVM 堆的垃圾回收机制(一)— 判活

垃圾回收分为两步:1)判定对象是否存活。2)将“消亡”的对象进行内存回收。

1 判定对象存活

可达性分析算法:通过一系列“GC Roots”对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走的路径为“引用链”,如果某个对象到“GC Roots”没有任何引用链相连,则判定该对象“消亡”。

强引用

正常引用,可达性分析搜索的只有强引用

软引用

关联还有用,但非必须的对象。在系统将要发生内存溢出时,会把这些对象列入回收范围。

弱引用

比软引用更弱,只能生存到下次垃圾收集发生之前。

虚引用

最弱,无法通过虚引用来取得一个对象实例。唯一作用是在则会个对象被回收时收到一个系统通知。

表 Java 的引用类型

1.1 三色标记

图 可达性分析算法“三色标记”演示过程

1.1.1“对象消失”

垃圾回收器与用户线程并发执行,若以下两个条件同时成立,对象消失必然发生。

  1. 用户线程将黑色对象指向一个白色对象。
  2. 用户删除所有从灰色对象到该白色对象的引用。

注意:对于在并发期间新建的对象,JVM会把其标记为黑色或灰色。

1.1.2 写屏障

写屏障是一段嵌入在对象引用赋值操作中的代码逻辑(类似AOP)。JVM通过写屏障实现两种方式来解决上面“对象消失”的问题。

增量更新

破坏第1个条件。当黑色对象插入新指向到白色对象时,写屏障将新插入引用记录下来,等并发扫描结束,再将记录过的引用关系中黑色对象集作为根,重新扫描一次。

效率更低。

原始快照

破坏第2个条件。当灰色对象要删除指向白色对象的引用关系时,写屏障将要删除的引用关系记录下来,等并发扫描结束,再将记录过的引用关系中白色对象集作为根,重新扫描一次。

会产生浮动垃圾。

表 “对象消失”的解决方案

1.2 GC Roots 对象

主要有:1) 栈中引用的对象。2)本地方法栈中引用的对象。3)方法区中静态属性引用的对象及常量引用的对象。4)JVM 内部的引用。5)被同步锁持有的对象等。

获取GC Roots 集必须在一个能保障一致性的快照中才能进行,因此需要STW(Stop The Word)。

1.2.1 栈帧

栈帧是单个线程在方法调用时在栈中分配的内存区域,用于存储方法的执行状态。每个方法从调用到执行完成,对应一个栈帧的入栈和出栈。

图 栈帧内存布局

局部变量表

存储方法的参数、局部变量以及部分中间结果。

以变量槽(Slot)为基本单位,每个Slot占用4个字节。对于8个字节的变量,占用两个连续的Slot。

索引分配:

非静态方法第0位Slot存储this引用。

方法参数从第1位依次存储。

局部变量按声明顺序分配Slot。

操作数栈

存储方法执行过程中的操作数。

动态链接

存储指向运行时常量池中该方法的符号引用。

方法返回地址

存储方法退出后需要返回到的调用者位置。

附加信息

行号表、局部变量表描述符等。

表 栈帧内存组成

1.2.2 OopMap

收集线程需要遍历方法栈中每一个栈帧,来收集被引用的变量。如果对栈帧的局部变量表进行全表扫描,很耗时。

OopMap(ordinary object pointer Map)普通对象地图,用于描述栈帧中对象引用的位置。它通常是一个位图,每个位对应局部变量表中一个槽位。1表示该槽位有对象引用,0表示没有。例如,假设局部变量表一共有8个槽位,其中只有第1个及第3个槽位有对象引用,则OopMap表示为10100000。

一个栈帧包含多个OopMap。

1.2.3 安全点

引发引用关系变化的指令很多,无法为每一条指令都生成对应的OopMap,只会在某些位置生成,这些位置被称为安全点。

安全点选择的原则:平衡线程响应速度和性能开销。

安全点常用位置:方法调用、循环末尾、异常处理路径等。

主动式中断

主流方案,当需要GC Roots收集时,JVM在内存中设置一个标识位,用户线程每次到达安全点都会轮询标识位,如果需要中断,则主动挂起。

被动式中断

通过操作系统信号强制中断线程,如果有用户线程未到达安全点,则恢复该线程,让其到达安全点后再中断。

表 安全点的实现方案

缺陷:

1)本地方法无法设置安全点。

2)对于未插入安全点但需要长时间执行的指令(如循环),如果在循环提中未插入安全点,则需要等待循环完成才能到达安全点。

3)如果线程在安全点被阻塞或sleep,因为其被唤醒的时间不能确定,JVM无法等到该线程到达安全点。

4)如果为线程阻塞或sleep指定插入安全点,则需要插入安全点的地方会增加,会加重程序的负担。

1.2.5 安全区域

在某段代码片段中,引用关系不会发生变化,这个区域任何地方开始收集GC Roots都是安全的,这个区域称为安全区域。

当用户线程执行到安全区域时,会标识自己已进入安全区域。这段时间里JVM要进行GC Roots收集就可不必中断在安全区域内的线程,当线程要离开安全区域时,会先检测JVM是否完成了GC Roots枚举,如果完成,则线程继续执行,否则等待。

安全区域的应用场景:

  1. 本地代码的执行,当用户线程进入本地方法时就标识自己进入了安全区域。
  2. 统一管理线程多种阻塞状态,只有线程处于阻塞状态,即视为进入安全区域。
  3. 避免“长时间无安全点”的僵局,如在循环体中没有安全点,则标识为进入了安全区域。

安全区域是对安全点的必要补充。

1.3 对象回收判定

要正式宣告对象死亡,最少要经历两次标记过程:

  1. 可达性分析后进行第1次标记。
  2. 对上面标记的对象进行筛选,如果这些对象实现了finalize()方法,则会调用这个方法,这是对象唯一次复活的机会,否则宣告对象死亡。

相关文章:

  • python3 -m http.sever 8080加载不了解决办法
  • 6个常见的Python设计模式及应用场景
  • Python实战:开发经典猜拳游戏(石头剪刀布)
  • MySQL事务全解析:从概念到实战
  • 【CXX-Qt】2.1.1 为 WebAssembly 构建
  • 汽车免拆诊断案例 | 2024 款路虎发现运动版车无法正常识别智能钥匙
  • Java EE 进阶:MyBatis
  • 【NLP】 11. 神经网络,线性模型,非线性模型,激活函数,感知器优化,正则化学习方法
  • SpringBoot配置文件加载优先级
  • 最大公约数(GCD)和最小公倍数(LCM)专题练习 ——基于罗勇军老师的《蓝桥杯算法入门C/C++》
  • 蓝桥杯2023年第十四届省赛真题-接龙数列
  • Linux后门程序工作原理的详细解释,以及相应的防御措施
  • c语言数据结构 双循环链表设计(完整代码)
  • Ubuntu版免翻墙搭建BatteryHistorian
  • freeswitch(开启抓包信息)
  • 观察RenderDoc截帧UE时“Event”代表什么
  • ssh 多重验证的好处:降低密钥长度,动态密码
  • 分布式任务调度
  • 事件响应计划:网络弹性的关键
  • C++ :try 语句块和异常处理
  • 马上评|这种“维权”已经不算薅羊毛,涉嫌犯罪了
  • 著名词作家陈哲逝世,代表作《让世界充满爱》《同一首歌》等
  • 市场监管总局召开平台企业支持个体工商户发展座谈会
  • 陕西一村民被冒名贷款40余万续:名下已无贷款,将继续追责
  • 最新研究:新型合成小分子可“精准杀伤”癌细胞
  • 中医的千年传承:网络科学描绘其演化之路|PNAS速递