• JVM(十六)—— 垃圾回收(二)


    对象的finalzation机制

    Java语言提供了对象终止(finalzation)机制来允许开发人员提供对象被销毁之前的自定义处理逻辑

    当垃圾回收其发现没有引用指向一个对象,即垃圾回收此对象之前,总会先调用这个对象的finalize方法
    在这里插入图片描述

    finalize方法位于Object类中,允许在子类中被重写,**用于在对象被回收时进行资源释放,**通常在这个方法中进行一些资源释放和清理的工作,比如关闭文件,套接字和数据库连接等。

    永远不要主动调用某个对象的finalize方法,应该交给垃圾回收机制调用。里有包括以下三点:

    • 在finalize时可能导致对象复活。
    • finalize方法的执行时间是没有保障的,它完全由GC线程决定,极端情况下,若不发生GC,则finalize方法将没有执行机会。
    • 一个糟糕的finalize会严重影响GC性能。

    由于finalize方法的存在,虚拟机中对象一般处于三中可能的状态。
    如果从所有的根节点都无法访问到某个独享,说明对象已经不再使用了。一般来说,此对象需要回收。但事实上,也并非是非死不可,这时候它们暂时处于缓刑阶段。一个无法触及的对象有可能在某一个条件下复活自己,如果这样,那么对它的回收就是不合理的,为此,定义虚拟机中的对象可能的三种状态:

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

    以上三中状态中,是由于finalize方法的存在进行的区分,只有在对象不可触及时才可以被回收。

    判定一个对象是否可回收,至少姚经理两次标记过程:

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

    垃圾清除阶段的算法

    当成功区分出内存中存活对象和死亡对象后,GC接下来的任务就是执行垃圾回收,释放掉无用对象所占用的内存空间,一遍有足够的的可用内存空间为新对象分配内存。

    目前在JVM中比较常见的三种垃圾收集算法是标记-清除算法(Mark-Sweep),复制算法(Copying),标记-压缩算法(Mark-Compact)。

    标记-清除算法(Mark-Sweep)

    标记-清除算法(Mark-Sweep)是一种非常基础和常见的垃圾收集算法。

    当堆中的有效内存空间被耗尽的时候,就会停止整个程序(也被称为stop the world ,STW),然后进行两项工作,第一项是标记,第二项是清除。

    • 标记:Collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录可达对象。
    • 清除:Collector对堆内存从头到尾进行先行的遍历,如果发现某个对象在其Header中没有标记为可达对象,则将其回收。

    在这里插入图片描述
    注意:这里所谓的清楚并不是真的置空,而是把需要清除的对象地址保存在一个空闲的地址列表中。下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够,就存放。

    缺点:

    • 效率不算高
    • 在进行GC的时候,需要停止整个应用程序,导致用户体验差。
    • 这种方式清理出来的空闲内存是不连续的,产生内存碎片。需要维护一个空闲列表。

    复制算法(Copying)

    为了解决标记-清除算法在垃圾收集效率方面的缺点,之后诞生了复制算法(Copying)。

    核心思想: 将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在是用的呢村中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收。

    在这里插入图片描述
    优点

    • 没有标记和清除过程,实现简单,运行高效。
    • 复制过去以后保证空间的连续性,不会出现碎片问题。

    缺点

    • 需要两杯的内存空间。
    • 对于G1这种分拆成大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小。

    如果系统中的垃圾对象很多,复制算法就不会很理想,因为复制算法需要复制的存活对象数量并不会太大,或者说非常低才行。

    应用场景:
    在新生代,对常规应用的垃圾回收,一次通常可以回收70%-9%的内存空间。而因为复制算法适合存活对象数量少的场景,所以现在的商业虚拟机都是用这种手机算法回收新生代。

    在这里插入图片描述

    标记-压缩算法(Mark-Compact)

    复制算法的高效性是建立在存活对象少,垃圾对象多的前提下的。这种情况在新生代经常发生,但是在老难带,耿长江的情况是大部分对象都是存活对象。如果依然使用复制算法,由于存活对象较多,复制的成本也将提高。因此,基于老年代垃圾回收的特性,需要使用其他的算法。

    标记清除算法的确可以应用在老年代中,但是该算法不仅执行效率低下,而且在执行完内存回收后还会产生内存碎片,所以JVM的设计者需要在此基础上进行改进。标记-压缩算法诞生。

    在这里插入图片描述
    第一阶段和标记清除算法一样,从根节点开始标记所有被引用的对象。
    第二阶段将所有的存活对象压缩到内存一端,按顺序排放。
    之后清理便捷外所有空间。

    标记压缩算法的最终效果等同于标记-清除算法执行完成后,在进行一次内存碎片整理。因此也成为了标记-清除-压缩算法。
    二者的本质差异在于标记清除算法是一种非移动时的回收算法,标记压缩是移动式的,是否移动回收后的存活对象是一项优缺点并存的风险决策。

    可以看到,标记的存回对象将会被整理,按照内存地址一次排列,而未被标记的内存会被清理掉。如此依赖,当我们需要给新对象分配内存时,JVM只需要迟永一个内存的起始地址即可,这比维护一个空闲列表显然少了很多开销。

    优点

    • 消除了标记清除算法当中内存区域分散的缺点,我们需要给新对象分配内存时,JVM只需要持有一个呢村的起始地址即可。
    • 消除了复制算法当中,内存减半的高额代价。

    缺点

    • 从效率上来说,低于复制算法。
    • 移动对象的同事,如果对象被其他对象引用,则需要调整引用的地址。
    • 移动过程中,需要全程暂停用户线程。即STW.
  • 相关阅读:
    【若依(ruoyi)】bootstrapTable 有选中行按钮可用,无选中行按钮不可用/单选和多选按钮样式
    研究生学术与职业素养讲座MOOC---期末复习(1-15)
    基于FPGA的VGA显示彩条、字符、图片
    单商户商城系统功能拆解16—供应商管理
    (主从-阻抗)论文阅读笔记2
    springboot与redis
    Vue封装路由跳转方法,vue-router对query传参进行加密解密
    SpringBoot整合Redis,基于Jedis实现redis各种操作
    函数栈简述
    关于TensorFlow控制依赖问题
  • 原文地址:https://blog.csdn.net/weixin_40920359/article/details/127794434