手记

你还不懂「JVM垃圾回收及收集器♻️(1)」么?

对象失是否存活

两种判断方法

引用计数算法

  • 在对象中添加一个引用计数器,每当有一个地方引用它时

  • 计数器值就加一;当引用失效时,计数器值就减一;

  • 任何时刻计数器为零的对象就是不可能再被使用的。

引用计数算法的缺陷

  • 如下面代码,两个对象互相引用导致无法回收♻️

  • 对象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%存 活的极端情况,所以在老年代一般不能直接选用这种算法。

标记-整理算法

  • 其中的标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理

  • 而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存

缺点

  • 如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活区域,

  • 移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作


作者:xiaoff


0人推荐
随时随地看视频
慕课网APP