精华内容
下载资源
问答
  • 2021-03-03 14:33:59

    在上一篇文章中,我们说到了jvm使用可达性算法来判断对象是否应该回收。可达性算法从特定的对象集合(即GC Roots Set)出发,逐一判断对象是否可达。具体来说,分成两个阶段:首先枚举所有GC Roots 对象,然后进行可达性分析。

    上篇文章列举了有哪些 GC Roots Set,可以看到还是包括挺多类型的,具体的对象数目就更多了。如果在GC时先暂停了用户线程再开始逐个枚举GCRoots对象,肯定需要花费很多时间。有什么方法能够快速枚举GCRoots对象呢?jvm采用OopMap这个数据结构来保存GCRoots对象,在GC时直接通过OopMap就可以得到GCRoots对象了。

    那么在什么时候保存GCRoots对象到OopMap呢?jvm引入了安全点概念。当线程运行到安全点时,会保存GCRoots对象到OopMap。进行GC时,GC线程会等所有的用户线程都到达最近的安全点后,再开始进行GC回收,因为只有线程到达安全点,才能保证OopMap是最新的,不会出错。

    那么怎么让所有的用户线程都到达最近的安全点呢?有两种方法,抢断式中断和主动式中断。抢断式中断是先让所有的用户线程全部暂停,如果发现某个线程没有到达安全点,就让它重新运行一段时间再中断,直到它到达安全点为止。主动式中断是先设置一个标志位,线程运行时不断轮询这个标志位,如果发现标志位为真,就运行到最近的安全点,然后中断挂起。现在几乎所有的jvm都采用主动式中断的方式来中断线程,执行GC操作。

    这里面有一种特殊情况,如果某个线程原来就处于睡眠或者阻塞状态怎么办?如果线程原来就处于这种“不运行”的状态,那肯定不能自己主动进行中断了。让GC线程等待“不运行”的线程重新运行,然后再采用主动式中断,从时间上是不可控的。那么还有什么方法吗?jvm引入了安全区的概论。安全区是指在一段代码片段之中,引用关系是不变的。因此,在这个区域中任意地开始垃圾回收都是安全的。当用户线程执行到安全区里面的代码时,会首先标识自己已经进入了安全区域。如果线程要离开安全区时,需要检查是否已经完成了GC。如果完成了,线程可以继续执行,如果没有完成,线程就需要一直等待,直到收到可以离开安全区的信号为止。

    通过暂停所有的用户线程,安全点和安全区,jvm保证了在进行GC时,引用关系是不变的,可以获取到最新的OopMap。

    获取到最新的OopMap后,可以得到所有GC Roots对象的集合,然后就可以进行可达性分析了。

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

    更多相关内容
  • 可达性分析算法

    千次阅读 2021-07-21 18:54:27
    相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。 相较于引用计数算法,这里的可达性分析就是...

    一 概念

    可达性分析算法:也可以称为根搜索算法、追踪性垃圾收集。

    相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。

    相较于引用计数算法,这里的可达性分析就是 Java、C# 选择的。这种类型的垃圾收集通常也叫作追踪性垃圾收集(Tracing Garbage Collection)。

    二 思路

    所谓 "GC Roots”根集合就是一组必须活跃的引用。

    基本思路:

    • 可达性分析算法是以根对象集合(GCRoots)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达。

    • 使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链(Reference Chain)。

    • 如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象己经死亡,可以标记为垃圾对象。

    • 在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象。

    三 GC Roots 可以是哪些?

    • 虚拟机栈中引用的对象

               比如:各个线程被调用的方法中使用到的参数、局部变量等。

    • 本地方法栈内 JNI(通常说的本地方法)引用的对象

    • 方法区中类静态属性引用的对象

               比如:Java类的引用类型静态变量

    • 方法区中常量引用的对象

               比如:字符串常量池(string Table)里的引用

    • 所有被同步锁 synchronized 持有的对象

    • Java虚拟机内部的引用。

               基本数据类型对应的 Class 对象,一些常驻的异常对象(如:NullPointerException、OutOfMemoryError),系统类加载器。

    • 反映 java 虚拟机内部情况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等。

    四 总结

    总结一句话就是,堆空间外的一些结构,比如虚拟机栈、本地方法栈、方法区、字符串常量池等地方对堆空间进行引用的,都可以作为 GC Roots 进行可达性分析。

    除了这些固定的 GC Roots 集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”地加入,共同构成完整 GC Roots 集合。比如:分代收集和局部回收(Partial GC)。

    如果只针对 Java 堆中的某一块区域进行垃圾回收(比如:典型的只针对新生代),必须考虑到内存区域是虚拟机自己的实现细节,更不是孤立封闭的,这个区域的对象完全有可能被其他区域的对象所引用,这时候就需要一并将关联的区域对象也加入 GCRoots 集合中去考虑,才能保证可达性分析的准确性。

    五 小技巧

    由于 Root 采用栈方式存放变量和指针,所以如果一个指针,它保存了堆内存里面的对象,但是自己又不存放在堆内存里面,那它就是一个 Root。

    六 注意

    如果要使用可达性分析算法来判断内存是否可回收,那么分析工作必须在一个能保障一致性的快照中进行。这点不满足的话分析结果的准确性就无法保证。

    这点也是导致 GC进行时必须“stop The World”的一个重要原因。

    即使是号称(几乎)不会发生停顿的 CMS 收集器中,枚举根节点时也是必须要停顿的。

    展开全文
  • 在之前关于可达性分析算法的介绍中我们讲过,我们需要先找出可固定作为 GC Roots 的节点,然后沿着引用链去寻找那些无用的垃圾对象。GC Roots 节点一般在全局性引用(例如常量和类静态属性)与执行上下文(例如栈帧...

    本文部分摘自《深入理解 Java 虚拟机第三版》


    根节点枚举

    在之前关于可达性分析算法的介绍中我们讲过,我们需要先找出可固定作为 GC Roots 的节点,然后沿着引用链去寻找那些无用的垃圾对象。GC Roots 节点一般在全局性引用(例如常量和类静态属性)与执行上下文(例如栈帧中的本地变量表)中,尽管目标明确,但查找过程要做到高效并非一件易事,若要逐个查找可作为起源的引用肯定需要消耗不少时间

    迄今为止,所有收集器在根节点枚举这一步骤时都是必须暂停用户线程,也即 Stop The World,因为如果在分析过程中出现根节点集合中对象的引用关系仍在不断变化的情况,分析结果的准确性也就无法保证了

    在对栈内存进行分析时,虚拟机会看哪些位置存储了 Reference 类型,如果发现某个位置确实存的是 Reference 类型,就意味着它所引用的对象这一次不能被回收。但问题是,栈帧的本地变量表里面只有一部分数据是 Reference 类型的,那些非 Reference 类型(基本数据类型)的数据对我们毫无用处,但我们还是不得不对整个栈全部扫描一遍,这是对时间和资源的一种浪费。在 HotSpot 的解决方案中采用了一组称为 OopMap 的数据结构来实现直接找到对象引用,一旦类加载动作完成,HotSpot 就会把栈中代表引用的位置全部记录下来,这样收集器在扫描时就可以直接得知这些消息了


    安全点

    尽管有了 OopMap,但如果引用关系经常变化,虚拟机就需要为每一条指令都生成对应的 OopMap,这将会占用大量的额外存储空间

    HotSpot 当然没那么笨,它只会在特定的位置去记录这些信息,这些位置被称为安全点(SafePoint)。有了安全点的设定,用户程序就必须执行到安全点才能暂停,而不是在代码指令流的任意位置随意停顿。安全点的选定不能太少,让收集器等待时间过长,也不能太频繁,导致增大运行时内存负担。安全点的位置选定基本上是以“是否具有让程序长时间执行的特征”为标准进行选定,“长时间执行”的最明显特征就是指令序列的复用,例如方法调用、循环跳转、异常跳转等,只有具有这些功能的指令才能产生安全点

    对于安全点,另外一个要考虑的问题就是,如何在垃圾收集发生时让所有线程都跑到最近的安全点。一般有两种方案可供选择:

    • 抢先式中断:垃圾收集发生时,系统首先把所有用户线程全部中断,如果发现有用户线程中断的地点不在安全点上,就恢复该线程执行,直至跑到安全点再中断。现实中几乎没有虚拟机会采用抢先式中断
    • 主动式中断:垃圾收集发生时,不直接对线程操作,而是设置一个标志位,各个线程在执行时会不停地主动去轮询这个标志,一旦发现标志位为真就在最近的安全点主动中断

    安全区域

    安全点看似解决了我们遇到的问题,但还有一个需要思考的点:如果某一个用户线程正好处于“不执行”状态该怎么办?所谓“不执行”就是没有分配处理器时间片,典型的场景如用户线程处于 Sleep 或 Blocked 状态,这时线程无法响应中断请求,自然也就不能走到安全点主动挂起自己,而虚拟机也不可能持续等待线程重新被分处理器时间片。对于这种情况,就需要引入安全区域(Safe Region)来解决

    安全区域是指能够确保在某一代码片段中,引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区域看作是被扩展拉伸了的安全点

    当用户线程执行到安全区域时,首先会标识自己已经进入安全区域,那样当这段时间里虚拟机要发起垃圾收集时就不必去管这些已经声明自己在安全区域内的线程了。当线程要离开安全区域时,会检查虚拟机是否已经完成了根节点枚举,如果完成了,就继续执行,否则一直等待,直到收到可以离开安全区域的信号为止


    记忆集与卡表

    之前在讲解分代收集理论时,提到为了解决对象跨代引用的问题,垃圾收集器会在新生代建立名为记忆集(Remember Set)的数据结构,避免将整个老年代加入 GC Roots。事实上,所有涉及部分区域收集行为的垃圾收集器都会面临相同的问题

    记忆集是一种用于记录非收集区域指向收集区域的指针集合的抽象数据结构,最简单的实现可以是数组,其中存放非收集区域中所有含跨代引用的对象。实际上,收集器只需要通过记忆集判断某一块非收集区域是否存在指向收集区域的指针即可,并不需要了解跨代指针的全部细节,因此我们可以适当选择更粗犷的记录粒度:

    • 字长精度:每个记录精确到一个机器字长,该字包含跨代指针
    • 对象精度:每个记录精确到一个对象,该对象里有字段含跨代指针
    • 卡精度:每个记录精确到一块内存区域,该区域有对象含跨代指针

    最常用的是第三种“卡精度”,使用一种称为“卡表”的方式去实现记忆集。这里要提的一点是,记忆集只是一种抽象的数据结构,卡表是记忆集的一种具体实现,两者的关系可以类比 Java 中的 Map 和 HashMap

    卡表最简单的形式可以是一个字节数组,HotSpot 虚拟机也确实这么做了。字节数组的每一个元素都对应其标识的内存区域中一块特定大小的内存块,这个内存块称为“卡页”。一个卡页的内存通常包含不止一个元素,只要卡页内有一个或多个对象的字段存在跨代指针,那就将对应卡表的数组元素标识为 1,否则为 0。发生垃圾收集时,只要筛选出卡表中变脏的元素,就能轻易地把它们加入 GC Roots


    写屏障

    如何维护卡表元素呢?例如它们何时变脏,谁来把它们变脏等。何时变脏的答案很明显,只要有其他分代区域的对象引用了本区域对象,那么对应的卡表元素就应该变脏,变脏时间点原则上应该发生在引用类型字段赋值的那一刻

    问题是如何变脏呢?HotSpot 虚拟机是通过写屏障(Write Barrier)技术来维护卡表状态的。写屏障可以看作是虚拟机对“引用类型字段赋值”这个动作的 AOP 切面,在引用对象赋值时会产生一个环形通知,供程序执行额外的动作。应用写屏障后,虚拟机就会为所有赋值操作生成对应的指令。尽管这个动作也会产生额外开销,但和 Minor GC 时扫描整个老年代相比根本不值一提

    卡表在高并发场景下还会面临伪共享(False Sharing)问题。现代中央处理器的缓存系统是以缓存行(Cache Line)为单位存储的,当多线程修改互相独立的变量,而这些变量恰好共享同一缓存行,则会导致性能降低。如果所有卡表元素共享同一缓存行,那么更新时有可能会出现伪共享问题。一种简单的解决方案是先检查卡表标记,只有当该卡表元素未被标记过时才将其标记为变脏


    并发的可达性分析

    可达性分析算法理论上要求全过程都基于一个能保障一致性的快照,即必须冻结全部用户线程。在根结点枚举阶段,由于 GC Roots 相比整个 Java 堆的全部对象毕竟还算极少数,且有各种优化技巧(如 OopMap),它带来的停顿可以说微不足道。但如果从 GC Roots 开始往下遍历对象图,那么这一阶段的停顿时间必然与 Java 堆容量成正比例关系:堆越大,存储的对象就越多,对象图结果越复杂,自然花的时间也越多

    因此,部分垃圾收集器是允许用户线程与收集器线程并发工作的,但如果在收集器标记对象的同时,用户线程修改了引用关系,就会产生两种后果:把原本应该消亡的对象错误标记为存活;把原本应该存活的对象错误标记为消亡。前一种还好一些,不过是产生浮动垃圾罢了,而后一种就非常致命了,程序肯定会因此发生错误。为了更好地说明这个问题,我们按照“是否访问过”为条件将对象标记为以下三种颜色:

    • 白色:表示对象尚未被垃圾收集器访问过
    • 黑色:表示对象已经被垃圾收集器访问过,且该对象的所有引用都已经被扫描
    • 灰色:表示对象已经被垃圾收集器访问过,但该对象至少还有一个引用没有被扫描

    前面提到过的将应该存活的对象错误标记为消亡这一现象称为“对象消失”问题,即原本应该是黑色的对象被误标为白色,这一问题当且仅当以下两个条件同时满足时才会发生:

    • 赋值器插入一条或多条从黑色对象到白色对象的新引用
    • 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用

    要想解决对象消失问题,只需破坏这两个条件的任意一个即可,由此产生了两种解决方案:增量更新(Incremental Update)和原始快照(Snapshot At The Beginning,SATB)

    增量更新破坏的是第一个条件,当黑色对象插入新的指向白色对象的引用,就将其记录下来,等并发扫描结束后,再将记录过的新引用关系中的黑色对象作为根,重新扫描一次

    原始快照破坏的是第二个条件,当灰色对象要删除指向白色对象的引用时,同样将其记录下来,等并发扫描结束后,再将记录过的引用关系中的灰色对象为根,重新扫描一次

    以上两种方式都是基于写屏障实现


    展开全文
  • 可达性分析(或根搜索算法、追踪性垃圾收集)可达性分析概述相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效率的特点,更重要的是**该算法可以有效地解决在引用计数算法中循环引用的问题,防止...

    可达性分析(或根搜索算法、追踪性垃圾收集)

    可达性分析概述

    相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效率的特点,更重要的是**该算法可以有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。**相较于引用计数算法,这里的可达性分析就是Java、C#选择的,这种类型的垃圾收集通常也叫做追踪性垃圾回收。

    基本思路

    1.可达性分析算法是以跟对象集合(GC Roots)为起始点,按照从上到下的方式搜索根对象集合所连接的目标对象是否可达。

    2.使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜搜所走过的路径称为引用链。

    3.如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象已经死亡,可以标记为垃圾对象。

    4.在可达性分析算法中,只有能够被对象集合直接或间接连接的对象才是存活对象

    2707fc8d565694ba0740069313bf037d.png

    GC Roots

    在Java语言中,GC Roots包括以下几类元素:

    1.虚拟机栈中引用的对象。比如各个线程调用的方法中使用到的参数、局部变量等。

    2.本地方法栈内JNI引用的对象。

    3.方法区中类静态属性引用的对象。比如Java类的引用类型静态变量。

    4.方法区中常量引用的对象。比如字符串常量池里的引用。

    5.所有被同步锁synchronized持有的对象

    等等

    f42ffa131e59b1958da73c0d7b00b60c.png

    小技巧:由于GC Roots采用栈方式存放变量和指针,所以如果一个指针,它保存了堆内存里面的对象,但是自己又不能存放在堆内存里面,那么它就是一个GC Roots。

    注意:如果要使用可达性分析算法来判断内存中是否可回收,那么分析工作必须在一个能够保障一致性的快照中进行,这点不能满足的话分析结果的准确性就无法保证。这点也是导致GC进行时必须"Stop The World"的一个重要原因,即使是号称(几乎)不会发生停顿的CMS收集器中,枚举根节点时也是必须要停顿的。

    本文地址:https://blog.csdn.net/weixin_48276298/article/details/109912153

    如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

    展开全文
  • 在java中是通过引用来和对象进行关联的,也就是说如果要操作对象,必须通过引用来进行。通过引用计数来判断一个对象是否可以被回收。如果一个对象没有任何引用与之...引用计数法的特点是实现简单,而且效率较高,但是它
  • JVM:可达性分析算法

    千次阅读 2019-09-03 23:21:11
    一、可达性分析算法 在Java中,是通过可达性分析(Reachability Analysis)来判定对象是否存活的。该算法的基本思路就是通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径...
  • 可达性分析算法: (当前主流语言的内存管理子系统都是通过可达性分析算法来判断对象是否存活,Java语言同样,没有使用引用计数法算法的) 基本思路:GC roots的对象作为起始节点集合,从这些节点开始,根据引用...
  • 三、可达性分析算法实现思路: 所谓的GC Roots根集合就是一组必须活跃的引用。 图解: 四、Java中 GC Roots 包括哪些元素: 图解: 五、GC Roots扩展: 六、注意: ...
  • 大白话理解可达性分析算法

    万次阅读 多人点赞 2020-10-24 21:39:29
    可达性分析算法 引用计数算法 在对象中添加一个引用计数器,每当新加一个引用时,计数器就加1,当引用失效时,计数器就减1。任何时刻只要计数器为0就代表对象没有引用可以被回收。 这种算法实现简单,判断高效,...
  • 即使在可达性分析算法中判定为不可达的对象,也不是“非死不可”的,这时候它们暂时还处于“缓 刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析后发现没 有与GC Roots相连接的...
  • 当前主流的商用程序语言(Java、C#,上溯至前面提到的古老的Lisp)的内存管理子系统,都是通过可达性分析(Reachability Analysis)算法来判定对象是否存活的。这个算法的基本思路就是通过一系列称为“GC Roots”的...
  • 引用计数算法(ReferenceCounting)的实现简单,判定效率也很高,但是在Java虚拟机里面没有选用引用计数算法来管理内存,其中最主要的原因是它很难解决对象之间相互循环引用的问题。 比如俩个对象objA和objB都有...
  • 相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。 相较于引用计数算法,这里的可达性分析就是...
  • 判断哪些内存需要回收引用计数算法、可达性分析算法。引用计数算法优点实现简单,但是无法判断循环引用。可达性分析算法:通过一系列的GC ROOTS作为起点,往下搜索,所走过的路径叫引用链。当一个对象没有引用链到GC...
  • JVM-可达性分析算法

    2021-04-11 14:13:05
    一、可达性分析算法 在Java中,是通过可达性分析(Reachability Analysis)来判定对象是否存活的。该算法的基本思路就是通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被...
  • 引用计数算法 在java中是通过引用来和对象进行关联的,也就是说如果要操作对象,必须通过引用来进行。那么很显然一个简单的办法就是通过引用计数来判断一个对象是否可以被回收。如果一个对象没有任何引用与之关联,...
  • 文章目录垃圾回收概述垃圾回收相关算法标记阶段:引用计数法标记阶段:可达性分析算法对象的finalization机制MAT的GC Roots溯源清除阶段:标记-清除算法清除阶段:复制算法清除阶段:标记-压缩算法小结分代收集算法...
  • GC 可达性分析算法

    千次阅读 2018-11-07 19:27:13
    在主流的商用程序语言的主要实现中,都是称通过可达性分析(Reachability Analysis)来判定对象是否存活的,这个算法的基本思路就是通过一系列的称为“GC Roots“的对象作为起始点,从这些节点开始向下搜索,搜索所...
  • 垃圾标记阶段算法包括引用计数算法和可达性分析算法。 在进行垃圾回收之前,需要判断哪些对象是存活对象,哪些是死亡对象,只有被标记为死亡的对象才能够被回收。 当一个对象已经不再被任何的存活对象继续引用的...
  • 上一篇:003-JVM-标记阶段的算法:怎么确定垃圾 https://blog.csdn.net/fsjwin/article/details/111322134 gc roots有哪些呢?这个是面试题,也要背下来,如果不想进大厂的话,可以不背。 1. 常规gc roots JVM ...
  • 可达性算法是判断对象是否能够被GC Root节点链接,来筛选存活对象和死亡对象的一种算法,这里的“链接”指的是Java语言当中的引用:强引用,软引用,弱引用,虚引用。 四种引用: 强引用 强引用就是指在程序代码中...
  • 对于可达性分析算法中不可达的对象,它们也不会立刻就被回收,这个时候它们暂时处于“嫌疑人”状态,到真正宣告一个对象死亡,至少要经历两次标记过程。 如果对象在进行可达性分析之后被发现没有与GC Roots相连...
  • JAVA垃圾回收-可达性分析算法

    万次阅读 多人点赞 2018-08-05 15:31:54
    在java中是通过引用来和对象进行关联的,也就是说如果要操作对象,必须通过引用来进行。那么很显然一个简单的办法就是...这种方式的特点是实现简单,而且效率较高,但是它无法解决循环引用的问题,因此在Java中并没...
  • 可达性分析之三色标记算法详解

    千次阅读 多人点赞 2021-03-28 16:14:13
    CMS算法的基础是通过可达性分析找到存活的对象,然后给存活的对象打个标记,最终在清理的时候,如果一个对象没有任何标记,就表示这个对象不可达,需要被清理。 并发标记阶段是从GC Root直接关联的对象开始枚举的
  • 相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效的解决在引用计数算法中循环引用的问题,防止内存泄露的发生。 相较于引用计数算法,这里的可达性分析...
  • 为了解决这个问题,就有了可达性分析回收算法可达性分析算法中定义了几个GC Root对象,这几个root对象在GC时不会被JVM回收掉,然后通过这些对象像树枝一样向外延伸,被引用到的对象说明还存活使用,就不会被GC...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 21,844
精华内容 8,737
关键字:

可达性分析算法实现