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

JVM 之垃圾回收相关算法



垃圾标记阶段的算法

垃圾标记阶段的主要任务:区分内存中哪些是存活对象,哪些是已经死亡的对象。只有被标记为已经死亡的对象,GC才会执行垃圾回收时,释放掉其所占用的内存空间。
简单来说,当一个对象已经不再被任何的存活对象继续引用是,就可以宣判为已经死亡。
一般有两种方式判断对象存活:引用计数算法可达性分析算法(HotSpot使用的是这种)

1.引用计数算法

  • 概念:
    • 对每个对象保存一个整型的引用计数器
  • 优点:
    • 实现简单,垃圾对象便于辨识,判定效率高,回收没有延迟性。
      -缺点:
    • 需要单独的字段存储计数器,增加存储空间的开销
    • 每次有新的引用都需要更新计数器,伴随着假发和减法操作,增加时间开销
    • 无法处理循环引用,所以Java的垃圾回收器中没有使用这类算法。
      循环引用
      从图中可以看到,p引用不再指向该循环链表,但是它们每个节点又互相引用,如果使用引用计数器,每个节点的引用计数器的值都为1,造成无法被回收。
  • 但是引用计数算法是很多语言的资源回收的选择,比如Python
    • 但是它是如何解决循环引用?
      • 手动解除
      • 使用弱引用weakref

2. 可达性分析算法(根搜索算法)

  • 优点:
    • 能够有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生
      -**java 、 C#**使用的是这种算法进行垃圾回收。
  • GCRoots,一组必须活跃的引用。
  • 算法基本思路:
    1. 该算法以根对象集合(GC Roots)为起点,从上至下搜索被跟对象集合所连接的目标对象是否可达
    2. 搜索之后,内存中存活对象都会被跟对象集合直接或间接链接,搜索所走过的路径称为引用链(Reference Chain)
    3. 如果想要回收的目标对象没有与任何引用链相连,则是不可达可以被标记为垃圾对象。
    4. 在该算法中,只有能够被跟对象集合直接或间接链接的对象才是存活对象。如下图所示。
      可达性分析存活对象
  • 在Java语言中,哪些元素可以成为跟对象?
    • 虚拟机栈中的引用对象: 局部变量表中的变量等。
    • 本地方法栈内JNI引用的对象。
    • 方法区中类变量 引用的对象
    • 方法区中常量引用的对象。
    • 所有被同步锁synchronized持有的对象。
    • Java虚拟机背不得引用
      • 比如 基本数据类型所属的Class对象、一些常驻的异常对象(NPException ,OOMError),系统类加载器。
    • 等等。
  • 由于Root采用栈方式存放变量和指针,所以如果一个指针,他保存了对内存里面的对象,但是自己又不在对内存里面,它就是一个Root
  • 注意
    • 如果要使用可达性分析算法来判断对象是否存活,必须保证一个能保障一致性的快照中进行,(如果不能保证一致性,有可能,你这边刚标记完存活,下一刻目标对象就不再被引用,或者这一刻目标对象不被引用,下一刻就会被引用),所以不满足这一点的话标记的准确性就无法保证。
    • 上面这个点也是导致进行GC是必须**STW(stop the world)**的一个重要原因。
    • 号称(几乎)永不停顿的CMS收集器也会在,枚举根节点时也是必须要停顿的

3.对象的finalization机制(最后的复活机会,仅有一次)

  • 该机制允许开发人员提供** 对象被销毁前的自定义处理逻辑**。

  • 来及回收目标对象之前,总会先调用这个对象的finalize()方法

  • object 中的finalize()方法允许被子类重写,用于对象被回收之前进行资源释放,比如关闭文件、数据库连接等。

  • 永远不要主动嗲用对象的finalize()方法,应该交给垃圾回收机制自行调用。

    • 在调用该方法时可能会导致对象复活。
    • 该方法的执行时间是没有保障的,它完全由GC线程决定,该方法的执行优先级很低。
    • 一个糟糕的finalize()会严重影响GC性能
  • 由于finalize()方法的存在,虚拟机中的对象一般处于三种可能的状态

    • 可触及的:从根节点开始,可达该对象。
    • 可复活的:对象的所有引用都被释放,但是对象有可能在finalize()中复活,比如重新引用该对象。
    • 不可触及的:该对象的finalize()被调用,并且没有复活,该对象就不可能再被复活,以为finalize()方法只会被调用一次。
  • 判断一个目标对象A 是否可回收,至少姚经理两次标记的过程:

    1. 如果A没有到GCROOTs的引用链,则第一次被标记。
    2. 进行筛选,判断A是否要必需执行finalize()方法:
      1. 如果A没有重写finaliz()方法,或者该方法已经被调用过一次,虚拟机会视为该方法没有必要执行,A就被判定为不可触及的
      2. A重写了finalize()方法,而且该方法没有被执行过,虚拟机会把A插入到F-Queue中,之后,会有一个虚拟机创建的、低优先级的Finalizer线程触发该对象的finalize()方法。
      3. fianlize()方法是对象逃脱死亡的最后机会,稍后GC会对F-Queue中的对象进行第二次标记。如果在第二次标记之前A在finalize()方法中与**与引用链上的任何一个对象建立了联系,那么第二次标记就会将该对象移除“即将回收”的集合, 如果再次出现没有被任何对象引用,finalize()不会再被调用。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值