JVM之垃圾回收标记阶段相关算法

661 阅读7分钟

这是我参与更文挑战的第 12 天,活动详情查看: 更文挑战

垃圾回收相关算法

在GC执行之前先判断内存中那些对象是死的,哪些是存活的,只有被标记已经死亡的对象才会被回收,这个阶段为垃圾标记阶段。当一个对象不被任何存活的对象引用时就判断为死亡。

1.垃圾标记-引用计数算法

  • 引用计数算法概述:对每个对象保存一个整型的引用计数器属性,用于对象被引用的情况,只要对象计数器的值为0,那就表示对象没有任何引用指向,可以被回收。
  • 引用计数算法优势:实现简单,垃圾对象便于辨识;判断效率高,回收没有延迟性。
  • 缺点:
    • 使用单独的字段存储计数器,增加存储空间的开销。
    • 每次赋值都要更新计数器,伴随着加法减法的操作,增加了时间开销。
    • **引用计数器无法循环引用,因为这个原因导致Java没有采用该算法。**循环引用:变量A--->对象1--->对象2--->对象1,这时让变量A--->null,虽然两个对象在栈空间都没有被变量引用,但是因为两个对象互相引用不能被标记上。

2.垃圾标记-可达性分析算法(根搜索算法、追踪性垃圾收集)

概述:可达性分析算法不仅具备实现简单和指向效率高的特点,还能有效的解决循环引用的问题,防止内存泄漏的发生。

image-20210610150615264

基本思路:

  • 可达性分析算法是以根对象集合(GC Roots)为起始点,按照从上到下的搜索方式搜索被根对象集合所链接的目标对象是否可达。
  • 使用可达性分析算法后,内存中的存活对象都会被根对象直接或间接的连接着,搜索所走过路径称为引用链。
  • 如果对象没有被任何引用链相连,则是不可达表示对象已经死亡可以被回收。

GCRoots主要包括下面内容:

  • JVM中栈中引用的对象,比如方法中的参数、局部变量等。
  • 本地方法引用的对象。
  • 方法区静态属性的引用变量。
  • 方法区常量引用对象,比如字符串常量池的引用。
  • 被同步锁持有的对象。
  • JVM内部的引用 比如异常对象、系统类加载器对象。
  • 除了上面固定的GC Roots集合以外,还会把其他对象临时加入GC Roots集合中。比如:分代收集器和局部回收,只回收新生代的时候,可以把不在新生代但是在堆空间的对象也当作临时的GC Roots。

由于GCRoot采用栈方式存储变量和指针,所以一个指针如果保存了堆内存的对象,但是自己有不在堆空间中,那么它就是一个Root。

**注意:**如果使用可达性分析算法判断内存是否可以回收,那么必须保障在一个一致性的快照中进行,否则准确性无法保证。也就是因为这个远,才导致了GC时的STW(停止用户线程)的原因。

3.对象finalization机制

概述:

  • Java提供了对象终止的机制。允许开发人员提供对对象销毁之前的自定义处理。
  • 垃圾回收器回收一个对象时会调用finalize()方法。
  • finalize()方法可以被子类重写,用于在对象回收时进行资源释放。

**注意:**不要主动调用finalize()方法,应该交给垃圾回收器调用。

由于finalization机制的存在,虚拟机中的对象一般处于三种可能的状态。

一般情况下,在可达性分析后所有的根节点无法触及的对象都是需要回收的,但是这样一个无法触及的对象可能在某种特定条件下“复活”自己,如果还能复活,那么回收是不合理的。为此,定义虚拟机对象有三种状态:

  • 可触及状态:从根节点可以触及的对象。
  • 可复活:对象的所有引用被释放,所有节点都不可达,但是对象可能在finalize()方法中复活。
  • 不可触及:对象的finalize()调用,并且没有被复活,那么就会进入不可复活状态,不可触及的对象不可能被复活,因为finalize()只会被调用一次。

判断一个对象是否可以回收,至少两次标记过程:

  • 判断是否有GC Roots引用,第一次标记。
  • 判断是否有必要执行finalize()方法:
    • 如果对象没有重写finalize()方法或者finalize()方法已经被虚拟机调用过,则没有必要执行finalize()方法,对象被判定为不可触及。
    • 如果对象重写了finalize()方法,并且还未执行,那么这个对象会被插入到一个F-Queue队列中,由一个虚拟机自动创建的、优先级低的Finalizer线程触发finalize()方法执行。
    • GC会对F-Queue队列中的对象进行第二次标记。如果对象和GC Roots上的任意对象建立了关系,那么第二次标记时,该对象会被移除即将回收的集合。在此之后,如果对象再次出现没有被引用的情况,finalize()方法 不会再被调用,对象会直接变为不可触及状态,一个对象的finalize()方法只会被调一次。finalize()是对象逃脱死亡的最后机会。

3.对象finalization机制

概述:

  • Java提供了对象终止的机制。允许开发人员提供对对象销毁之前的自定义处理。
  • 垃圾回收器回收一个对象时会调用finalize()方法。
  • finalize()方法可以被子类重写,用于在对象回收时进行资源释放。

**注意:**不要主动调用finalize()方法,应该交给垃圾回收器调用。

由于finalization机制的存在,虚拟机中的对象一般处于三种可能的状态。

一般情况下,在可达性分析后所有的根节点无法触及的对象都是需要回收的,但是这样一个无法触及的对象可能在某种特定条件下“复活”自己,如果还能复活,那么回收是不合理的。为此,定义虚拟机对象有三种状态:

  • 可触及状态:从根节点可以触及的对象。
  • 可复活:对象的所有引用被释放,所有节点都不可达,但是对象可能在finalize()方法中复活。
  • 不可触及:对象的finalize()调用,并且没有被复活,那么就会进入不可复活状态,不可触及的对象不可能被复活,因为finalize()只会被调用一次。

判断一个对象是否可以回收,至少两次标记过程:

  • 判断是否有GC Roots引用,第一次标记。
  • 判断是否有必要执行finalize()方法:
    • 如果对象没有重写finalize()方法或者finalize()方法已经被虚拟机调用过,则没有必要执行finalize()方法,对象被判定为不可触及。
    • 如果对象重写了finalize()方法,并且还未执行,那么这个对象会被插入到一个F-Queue队列中,由一个虚拟机自动创建的、优先级低的Finalizer线程触发finalize()方法执行。
    • GC会对F-Queue队列中的对象进行第二次标记。如果对象和GC Roots上的任意对象建立了关系,那么第二次标记时,该对象会被移除即将回收的集合。在此之后,如果对象再次出现没有被引用的情况,finalize()方法 不会再被调用,对象会直接变为不可触及状态,一个对象的finalize()方法只会被调一次。finalize()是对象逃脱死亡的最后机会。