JVM垃圾回收及收集器♻️

对象失是否存活

两种判断方法

引用计数算法

  • 在对象中添加一个引用计数器,每当有一个地方引用它时
  • 计数器值就加一;当引用失效时,计数器值就减一;
  • 任何时刻计数器为零的对象就是不可能再被使用的。
    引用计数算法的缺陷
  • 如下面代码,两个对象互相引用导致无法回收♻️
  • 对象objA和objB都有字段instance,赋值令 objA.instance=objB及objB.instance=objA,
  • 除此之外,这两个对象再无任何引用,实际上这两个对象已 经不可能再被访问,
  • 但是它们因为互相引用着对方,导致它们的引用计数都不为零,引用计数算法也 就无法回收它们。
Test objA = new Test();
        Test objB = new Test();
        objA.instance = objB;
        objB.instance = objA;
        objA = null; 
        objB = null;

可达性分析算法

基本思路

  • 通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,

  • 搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,

  • 或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。

  • 可以作为GC Roots的对象包括

引用类型的静态变量、字符串常量池里的引用、本地方法栈中引用的对象
基本数据类型对应的Class对象、同步锁持有的对象

四种引用

强引用

  • 指在程序代码之中普遍存在的引用赋值,即类似Object obj=new Object()这种引用关系。
  • 无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回 收掉被引用的对象。

软引用

  • 在系统将要发生内 存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存, 才会抛出内存溢出异常。

弱引用

  • 它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。
  • 当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

虚引用

  • 它是最弱的一种引用关系。一个对象是否有虚引用的 存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。
  • 为一个对象设置虚 引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。

执行"缓刑"之finaliz

  • 如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记
  • 随后进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。
  • 假如对象没有覆盖finalize()方法,或者finaliz e()方法已经被虚拟机调用过
  • 那么虚拟机将这两种情况都视为“ 没有必要执行”
package com.example.testkfk;

/**
 * TODO
 *
 * @author xiaoff
 * @version 1.0
 * @date 2021/7/18 1:36 下午
 */
public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;
    public void isAlive() {
        System.out.println("yes, i am still alive :)");
    }


    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed!");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }
    public static void main(String[] args) throws Throwable {
        SAVE_HOOK = new FinalizeEscapeGC();
  //对象第一次成功拯救自己
        SAVE_HOOK = null;
        System.gc();
  // 因为Finalizer方法优先级很低,暂停0.5秒,以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null) { SAVE_HOOK.isAlive();
        } else {
            System.out.println("no, i am dead :(");
        }
  // 下面这段代码与上面的完全相同,但是这次自救却失败了
        SAVE_HOOK = null;
        System.gc();
  // 因为Finalizer方法优先级很低,暂停0.5秒,以等待它 
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no, i am dead :("); }
    }
}

  • 输出信息为

  • SAVE_HOOK对象对 象 的 finalize( ) 方法确实被垃圾收集器触发过,并且在被收集前成功逃脱了。

  • 而且下面两个代码段是一样的,从输出结果可以说明,只免死了一次,第一次逃脱,第二次失败

  • 所以总结来说任何一个对象的finalize()都只会被系统自动调用一次

垃圾收集器

标记-清除算法

  • 算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,
  • 标记过程就是对象是否属于垃圾的判定过程
    缺点
  • 执行效率不稳定,java堆中有大对象,而回收的时候随着对象数量的增长而降低
  • 内存空间的碎片化问题,标记、清除之后会产生大 量不连续的内存碎片

标记复制算法

  • 将可用 内存按容量划分为大小相等的两块,每次只使用其中的一块。

  • 当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
    缺点

  • 在对象存活率较高时就要进行较多的复制操作,效率将会降低。

  • 更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存 活的极端情况,所以在老年代一般不能直接选用这种算法。

标记-整理算法

  • 其中的标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理
  • 而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存
    缺点
  • 如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活区域,
  • 移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作

经典垃圾收集器

Serial收集器

  • 这个收集器是一个单线程工作的收集器,但它的“ 单线 程”的意义并不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,
  • 更重要的是强调在它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。

ParNew收集器

  • ParNew收集器实质上是Serial收集器的多线程并行版本

    收集器中的并发与并行
  • 并行(Parallel):并行􏰀述的是多条垃圾收集器线程之间的关系
  • 说明同一时间有多条这样的线 程在协同工作,通常默认此时用户线程是处于等待状态。
  • 并发(Concurrent):并发􏰀述的是垃圾收集器线程与用户线程之间的关系
  • 说明同一时间垃圾 收集器线程与用户线程都在运行。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于 垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。

Parallel Scavenge收集器

  • 基于标记-复制算法实现的收集器,也是能够并行收集的多线程收集器
  • 供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数
  • 以及直接设置吞吐量大小的-XX:GCTimeRatio参数。

Serial Old收集器

  • Serial Old是Serial收集器的老年代版本,
  • 它同样是一个单线程收集器,使用标记-整理算法

Parallel Old收集器

  • Parallel Old是Parallel Scavenge收集器的老年代版本
  • 支持多线程并发收集,基于标记-整理算法实现。

CMS

  • 以获取最短回收停顿时间为目标的收集器
    四个步骤
  1. 初始标记(CMS initial mark)
  2. 并发标记(CM S concurrent mark)
  3. 重新标记(CM S remark)
  4. 并发清除(CMS concurrent sweep)

优点

  • 并发收集、低停顿
    缺点
  • 它虽然不会导致用户线程停顿,但却会因为占用了一部分线程而导致应用程序变慢,降低总吞吐量
  • 在CMS的并发标记和并发清理阶段,用户线程是还在继续运行的,
  • 程序在运行自然就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后,
  • CMS无法在当次收集中处理掉它们,只好留待下一次垃圾收集 时再清理掉。这一部分垃圾就称为“ 浮动垃圾”

Garbage First收集器

  • 遵循分代收集,开创的基于Region的堆内存布局是它能够实现这个目标的关键
  • 初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要 停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际 并没有额外的停顿。
  • 并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,递归扫􏰁整个堆 里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫􏰁完成以后 , 还要重新处理SATB记录下的在并发是引用的并发对象
  • 最终标记(Final Marking):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留 下 来 的 最 后 那 少 量 的SATB记录 。
  • 筛选回收(Live Data Counting and Evacuation):负责更新Region的统计数据,对各个Region的回 收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region 构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行 完成的。
posted @ 2021-07-18 14:58  xiaoff  阅读(54)  评论(0编辑  收藏  举报