美文网首页
Java虚拟机垃圾收集

Java虚拟机垃圾收集

作者: 睦月MTK | 来源:发表于2020-03-10 18:23 被阅读0次

    statement:本篇内容只是建立在我目前经验的基础之上,必然有不完善甚至是不正确的地方,请谨慎阅读,如果能指出错误与不足之处,更是不甚感激


    一、垃圾收集过程简述

    虚拟机的垃圾收集过程大致可以分为以下几步:

    1. 找出需要被收集的对象
    2. 如果被收集的对象没有执行过finalize方法,则将其加入到F-Queue队列中等待被执行finalize方法
    3. 根据不同的收集算法,进行真正的垃圾收集

    二、标记阶段

    标记阶段的作用就是找出Java堆中哪些对象时需要被收集的并标记,以便虚拟机收集。判断需要被收集的对象有两种算法:

    • 引用计数算法
      引用计数算法原理就是给每个引用设置一个被引用计数器,如果对象被引用一次,则被引用计数器+1,如果在标记阶段发现有被引用计数器为0,则将其标记为待回收对象。
      但是!!!该算法有一个致命缺陷,那就是该算法无法正确判断互相引用的对象的生命状态,如A内部引用B,B内部引用A,则A和B两个对象的被引用计数器都不为0,永远不会被垃圾收集器收集
    • 可达性分析算法(根节点枚举)
      可达性分析算法时目前主流的垃圾收集中标记过程的算法,它完美的解决引用计数算法带来的互相引用的问题。
      • 原理:从一些基本不会“突然消失”的对象开始,逐级分析其引用链,在整个分析过程后,如果还有不在引用链中的对象,则这些对象可以被判断为可收集的
      • 一些基本不会突然消失的对象(根节点 GC Roots):
        • 虚拟机栈中引用的对象
        • 类的静态字段引用的对象
        • 方法区中常量引用的对象
        • 本地方法栈中引用的对象
      • 标记过程中的线程停顿(Stop The World):由于为了准确描述对象之间的引用关系,必须使得在根节点枚举期间,所有的引用关系不能变化,所以在该期间,将停止所有线程。
      • OopMap:为了减少线程停顿的时间,除去引用链分析的过程外,可以加速根节点枚举过程的方法还剩下查找根节点本身一项。通过OopMap的数据结构,虚拟机不需要再繁复的检查虚拟机栈中的上下文,找出引用的位置,然后再进行根节点枚举,因为OopMap准确的记录了上下文中的引用位置,该记录过程发生在类加载完之后。
      • 安全点:安全点就是指令流中的一个特定位置,安全点有以下特征:
        • 记录着OopMap
          生成太多的OopMap毕竟浪费空间,所以只在“安全点”记录OopMap
        • Stop The World中的线程停顿点
          线程毕竟只有在到达记录有OopMap的地方停顿才有意义,不然怎么调用OopMap进行根节点枚举
        • 具有让程序长时间执行的特质
          这是安全点选取的依据。一般是诸如方法调用、循环跳转、异常跳转等地方。
      • 线程中断的方式:线程并不是遇到GC事件就立即中断的,而是要到达安全点时才进行中断,保证线程到达安全点时再中断有两种方式:
        • 抢先式中断:GC事件发生时,立即中断所有线程,如果有线程不在安全点,则恢复线程让其执行到安全点,然后再中断
        • 主动式中断:线程每到一个安全点就去询问一遍是否发生GC事件了,如果发生了则中断。

    三、finalize方法执行阶段

    如果一个被标记的对象没有执行过finalize方法且finalize方法是被重写过的,则虚拟机会将这个对象加入F-Queue队列中,等待被执行finalize方法,需要注意的是,finalize方法不一定会执行完全,因为有一个超时时间,所以这也是为什么不建议使用finalize方法来达成类似析构函数的作用。如果在执行finalize方法时重新将该被标记的对象链接到任意一个没有被标记对象上,那么该对象在下一次标记时就会被取消标记,这样这个对象就被“救活了”。但是毕竟finalize只会执行一次,所以救命行为也就只有一次。


    四、垃圾收集阶段

    垃圾收集阶段才是真正的开始对无用对象进行销毁的阶段,实现该目的的算法有很多种:

    • 标记-清除算法
      • 原理:简单的进行标记,然后直接清除对应的对象
      • 优点:简单快捷
      • 缺点:造成空间碎片化,虚拟机会因为无法找到连续的空间而发生异常
    • 复制算法
      • 原理:将Java堆分出两块区域(Eden和Survivor),复制算法其实就是标记-复制-清除算法,它将Eden空间中未标记的对象存入Surivor中,然后再直接清除整个Eden空间
      • 优点:可以获得连续的空间
      • 缺点:如果未被标记的对象特别多的话,会严重影响性能,且会出现未被标记的对象内存总量大于Survivor区的情况,故该算法一般用于新生代(即该区域的对象基本都是朝生夕死的,存活下来的对象很少)
    • 整理算法
      • 原理:实际上就是标记-整理-清除算法,它将未被标记的对象移动到内存一端,然后再集中清理掉边界外的内存
      • 优点:可以获得连续的空间,且解决了复制算法带来的潜在问题
      • 缺点:不如复制算法高效,常用在不经常进行gc的且存活对象比例较大的区域(老年代)
    • 分代收集算法
      其实这只是一种垃圾收集思想,配合其他垃圾收集算法一起使用。虚拟机将Java堆分为新生代和老年代,新生代里的对象都是朝生夕死的小对象,老年代的对象都是些大对象且生命力强的。由于这两个区域的特点不同,应用不同的垃圾收集算法将会带来更高的效率
      • 对象分配策略:
        • 对象优先在Eden中分配
        • 超过阈值(-XX:PretenureSizeThreshold设置)的大对象将会直接分配进老年代
        • 对象的年龄超过阈值(-XX:MaxTenuringThreshold设置)的分配进老年代
        • 如果存在于Survivor区域的某个年龄的对象们占用总内存超过Survivor的一半,则等于或者大于该年龄的对象全部进入老年代
        • 为了避免新生代对象晋升至老年代时不会发生内存不够用而进行老年代gc的情况,可以设置-XX:-HandlePromotionFailure来允许虚拟机可以根据历次晋升对象的总大小的平均值来判断是否需要进行老年代gc为晋升对象提供空间。而不是简单的根据老年代剩余的最大连续空间是否比整个新生代大来判断

    五、其他
    • 对象引用的四种类型
      • 强引用:就是普通的引用
      • 软引用:使用SoftReference类来指定,软引用的对象将会在即将发生内存不足问题时被强制收集
      • 弱引用:使用WeakReference类来指定,弱引用的对象会在下一次垃圾收集时被强制收集
      • 虚引用:使用PhantomReference类来指定,是不可以通过虚引用来获取一个对象的,虚引用的存在仅仅是为了在被其指定的对象被收集时,获得一个通知
    • 方法区的回收
      方法区也会进行垃圾收集,只是次数很少,条件很苛刻,回收的对象包括废弃常量和无用的类。需要注意的是,即使满足条件可以被回收,也仅仅只是“可以”的程度,不是必然被回收的。
      无用的类判断条件如下:
      • 该类的所有实例必须不存在
      • 该类的ClassLoader必须不存在
      • 该类对应的Class对象必须不存在

    参考文档:
    [1] [深入理解Java虚拟机 -- 周志明]

    相关文章

      网友评论

          本文标题:Java虚拟机垃圾收集

          本文链接:https://www.haomeiwen.com/subject/dqdvdhtx.html