欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 新车 > 深入探究 JVM 堆的垃圾回收机制(一)— 判活

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

2025/3/21 18:04:03 来源:https://blog.csdn.net/qq_25308331/article/details/146407919  浏览:    关键词:深入探究 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()方法,则会调用这个方法,这是对象唯一次复活的机会,否则宣告对象死亡。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词