对jvm中 GC Roots 枚举的理解

码海撷星者
• 阅读 4091

对 GC Roots 枚举的理解

这里,只是在理论上讲,全堆垃圾收集。不包含现实中垃圾收集器的具体实现,也就是不包含分代、分区垃圾收集,其实,也就多一个跨代引用、跨区引用的问题。

啥是 GC Roots ?

凡是有堆外引用的对象,都是 GC Root。

局部变量(栈里)、静态变量和常量(方法区里),它们都是堆外的,只要引用了对象,那这个对象就是 GC Root ,就会被加入GC Roots 集合。


设想,这个世界上还没有垃圾收集器,而我想要发明一个,那遇到的第一个问题就是,我该如何判断一个对象是否已经死了。有一个方法,就是从当前肯定活着的对象入手。如果,我能把所有活着的对象找齐了,那剩下的就是死的了,逻辑上就是这么简单。对于活对象,我更愿意称它们为有用的对象,因为这更能表达它不应该被清理的特征。

只要两个条件,就可以找到jvm里所有,有用的对象:

1、栈、方法区等,非堆的内存区域,持有的对象。

堆里存了所有的对象,它们就像仓库里的工具,只有被使用才是有用的。被哪里使用呢?被堆外的其他区域。


2、第一个条件里的对象,在堆内使用的所有对象。

使用的意思是,它调用的对象,所调用的对象,所调用的对象...(套娃中)。是一个调用链。


GC Roots 就是为了实现第一个条件,它要找到所有被堆外区域引用的对象。

可达性分析,就是为了实现第二个条件,它要找到 GC Roots 所使用的所有对象。



GC Roots 枚举的过程

stop the world

GC Roots 枚举,必然需要暂停所有用户线程。

原因很好理解:GC Roots 枚举时,统计的就是 堆外 引用的对象。只拿栈中来说,如果客户线程依旧在运行,那么统计过程中,不断有栈帧出栈和入栈。新入栈的栈帧,其中的局部变量,也有可能是不会被统计到 GC Roots 里。(看完OopMap的机制,再更新详细内容)



safe point

在任何时候,都可以暂停用户线程。为啥要设置 safe point ?

理解这个问题,可能需要先搞懂 OopMap,但是,我的理解暂时还不透彻,所以只能笼统的讲。

用户线程暂停后,接下来会进行 GC Roots 枚举,而 GC Roots 枚举,却依赖 OopMap 存储的信息。但是,OppMap 不能每时每刻都更新数据,因为那样的话,对资源的消耗就太大了。

所以,要给 OopMap 更新数据,找一个节点,这个节点就是 safe point 。

在选取 safe point 时,只会选择在循环调用递归 。。。这是为什么呢?

JVM暂停用户线程的时候,都是采用 主动式暂停 ,所以以下就用 主动式暂停 来讨论。当 "stop the world"发生时,线程应该尽快跑到安全点,然后停下来。如果在选取安全点的时候,选在了 循环语句 的后面,那就太蠢了,白白增加了停顿时间。所以,肯定应该选在 循环语句 运行之前,基本就是在这个 循环语句 的地方。

总结,就是安全点的选择,会在长时间运行的地方,比如 循环调用 等地方。并在这些代码运行之前,暂停线程,避免浪费时间。



safe region

safe region 是 safe point 的补丁。

如果,在 "stop the world" 发生时,有些用户线程没有运行,比如 sleep 了。那么,这条线程, 在 "stop the world" 发生时,它不是运行状态,所以它并不能自行跑到安全点,并中断线程。而是,一直停在了非安全点的位置,一旦获取了时间片,它还是要运行的。

可能会在,下一个致命的时期,也就是 GC Roots 枚举时,这条线程如果又跑起来了。

那这个漏洞就太大了。

所以,又引入了 safe region,来补这个漏洞。


当用户线程执行到安全区域里面的代码时,首先会标识自己已经进入了安全区域,那样当这段时间里虚拟机要发起垃圾收集时就不必去管这些已声明自己在安区域内的线程了。

当线程要离开安全区域时,它要检查虚拟机是否已经完成了根节点枚举(或者垃圾收集过程中其他需要暂停用户线程的阶段),如果完成了,那线程就当作没事发生过,继续执行;否则它就必须一直等待,直到收到可以离开安全区域的信号为止。

——摘抄自《深入理解java虚拟机》第三版



总结

GC Roots 枚举前。首先,要暂停用户线程,会使用 safe point 和 safe ragion ,辅助暂停用户线程。然后,开始 GC Roots 枚举,并不会去遍历方法区、栈里的引用,而是,直接从OopMap里获取引用。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
3年前
java GC算法 垃圾收集器
GC算法垃圾收集器概述垃圾收集GarbageCollection通常被称为“GC”,它诞生于1960年MIT的Lisp语言,经过半个多世纪,目前已经十分成熟了。jvm中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,栈帧随着方
Peter20 Peter20
4年前
mysql中like用法
like的通配符有两种%(百分号):代表零个、一个或者多个字符。\(下划线):代表一个数字或者字符。1\.name以"李"开头wherenamelike'李%'2\.name中包含"云",“云”可以在任何位置wherenamelike'%云%'3\.第二个和第三个字符是0的值wheresalarylike'\00%'4\
Wesley13 Wesley13
3年前
JAVA面试考点解析(11)
9、解释内存中的栈(stack)、堆(heap)和方法区(methodarea)的用法。答:通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用JVM中的栈空间;而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
JVM笔记(4)
第三章垃圾收集器和内存分配策略  第三节垃圾收集算法   1.分类:引用计数式垃圾收集(直接垃圾收集) 和追踪式垃圾收集(间接垃圾收集)   2.分代收集理论:1)弱分代假说:主要是一些朝生夕灭的对象。                 2)强分代假说:这里是一些可以熬过多次垃圾收集器收集的
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(