常见的垃圾回收算法

115 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第25天,点击查看活动详情

1.确定垃圾的算法:

  • 引用计数法(无法解决循环引用,a指向b,b指向c,c指向a,但是a、b、c都不是根对象)

    • 在java中我们不适用这个算法,使用的是根可达性算法。
    • python中使用的是这个算法,这可能也是它的性能不如java的原因之一。

1655735492387.jpg

  • 根可达性算法(三色标记算法,如上图)
    • 三种颜色

      • 白色:没有检查(或者检查过了,确实没有引用指向它了)
      • 灰色:自身被检查了,成员没被检查完(可以认为访问到了,但是正在被检查,就是图的遍历里那些在队列中的节点)
      • 黑色:自身和成员都被检查完了
    • 假设现在有白、灰、黑三个集合(表示当前对象的颜色),其遍历访问过程为:

      • 初始时,所有对象都在 【白色集合】中;
      • 将GC Roots 直接引用到的对象 挪到 【灰色集合】中;
      • 从灰色集合中获取对象:
        1. 将本对象 引用到的 其他对象 全部挪到 【灰色集合】中;
        2. 将本对象 挪到 【黑色集合】里面。
      • 重复步骤3,直至【灰色集合】为空时结束。
      • 结束后,仍在【白色集合】的对象即为GC Roots 不可达,可以进行回收。
    • 什么是根?根就可以理解为main方法你main方法就是根,你这这里new出来的对象

      • 通过System Class Loader或者Boot Class Loader加载的class对象(通过自定义类加载器加载的class不一定是GC Root)

      • 处于激活状态的线程

      • 栈中的对象

      • JNI栈中的对象

      • JNI中的全局对象

      • 正在被用于同步的各种锁对象

      • JVM自身持有的对象,比如系统类加载器等。

image-20220615175843512.png

2垃圾回收算法

  • 标记清除(Mark-Sweep)

    • 缺点:
    • 1.造成内存碎片化 ;
    • 2 .分配效率较低,如果是一块连续的内存空间,那么我们可以通过指针加法(pointer bumping)来做分配。而对于空闲列表,Java 虚拟机则需要逐个访问列表中的项,来查找能够放入新建对象的空闲内存。
  • 标记压缩(标记清除的升级)(Mark-Compact)

    • 能够解决碎片化问题,但是压缩算法的性能开销是比较大的,因为需要挪动对象,然后改变对象引用的地址。
  • 复制算法(Coping)

    • 能够解决碎片化问题,但是空间使用效率变低了,毕竟有一块空间是用不着的。
    • 复制算法是将内存划分为两个区间,在任意时间点,所有动态分配的对象都只能分配在其中的一个区间(称为活动区间),而另外一个区间(空闲区间)是空闲的。
    • 当有效内存空间耗尽时,JVM将暂停程序运行,开启复制GC线程。接下来GC线程会将活动区的存活对象,全部复制到空闲区间,且严格按照内存地址依次排列,与此同时,GC线程将更新存活对象的内存引用地址指向新的内存地址。
    • 此时,空闲区间已经与活动区间交换,而垃圾对象现在已经全部留在了原来的活动区间,也就是现在的空闲区间。事实上,在活动区间转换为空闲区间的同时,垃圾对象已经被一次性全部回收了。

三种算法在垃圾回收器中搭配使用。