什么时候回收?

垃圾回收—对象是否已死

  • 判断对象是否存活—引用计数算法❶

实现原理:给每个对象添加一个引用计数器,每当此对象被某个地方引用时,计数值+1;引用失效时,计数值-1。当计数值为0时,表示对象已经不能被使用。

  • 引用计数算法的特点

    • 实现简单、效率高,但是无法检测出循环引用❷。

  • 判断对象是否存活-可达性分析算法❸

    • 可达性分析算法

               在主流的商用程序语言如Java、C#等的主流实现中,都是通过可达性分析(Reachability Analysis)来判断对象是否存活的。此算法的基本思路就是通过一系列的“GC Roots”的对象作为起始点,从起始点开始向下搜索到对象的路径。搜索所经过的路径称为引用链(Reference Chain),当一个对象到任何GC Roots都没有引用链时,则表明对象“不可达”,即该对象是不可用的。

在Java语言中,可作为GC Roots的对象包括下面几种:

  • 栈帧中的局部变量表中的reference引用所引用的对象,譬如各个线程被调用的方法堆栈使用到的参数、局部变量、临时变量等

  • 方法区中类静态属性引用的对象。譬如Java类的引用类型静态变量。

  • 方法区中final常量引用的对象

  • 本地方法栈中JNI(Native方法)引用的对象

  • Java虚拟机内部的引用, 如基本数据类型对应的Class对象, 一些常驻的异常对象(比如 NullPointExcepiton、 OutOfMemoryError) 等, 还有系统类加载器。

  • 所有被同步锁(synchronized关键字) 持有的对象。

  • 反映Java虚拟机内部情况的JMXBean、 JVMTI中注册的回调、 本地代码缓存等。

  • 判断对象是否存活

即使在可达性分析算法中判定为不可达的对象, 也不是“非死不可”的, 这时候它们暂时还处于“缓 刑”阶段, 要真正宣告一个对象死亡, 至少要经历两次标记过程:

第一次标记:

如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链, 那它将会被第一次标记, 随后进行一次筛选, 筛选的条件是此对象是否有必要执行finalize()方法。

没有必要:

假如对象没有覆盖finalize()方法, 或者finalize()方法已经被虚拟机调用过, 那么虚拟机将这两种情况都视为“没有必要执行”。

有必要:

如果这个对象被判定为确有必要执行finalize()方法, 那么该对象将会被放置在一个名为F-Queue的 队列之中, 并在稍后由一条由虚拟机自动建立的、 低调度优先级的Finalizer线程去执行它们的finalize() 方法。finalize()方法是对 象逃脱死亡命运的最后次机会, 稍后收集器将对F-Queue中的对象进行第二次小规模的标记, 如果对 象要在finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可, 譬如把自己 (this关键字) 赋值给某个类变量或者对象的成员变量, 那在第二次标记时它将被移出“即将回收”的集 合;如果对象这时候还没有逃脱, 那基本上它就真的要被回收了。

注意:

Finalizer线程去执行它们的finalize() 方法, 这里所说的“执行”是指虚拟机会触发这个方法开始运行, 但并不承诺一定会等待它运行结束。这样做的原因是, 如果某个对象的finalize()方法执行缓慢, 或者更极端地发生了死循环, 将很可能导 致F-Queue队列中的其他对象永久处于等待, 甚至导致整个内存回收子系统的崩溃。

附:

  • 引用计数算法常用来说明垃圾回收算法的机制,但是很少为各种语言所有,其中COM采用的就是引用计数算法(未验证)。

  • 循环引用

    • 当对象A和对象B相互引用了对方作为自己的成员变量,只有当自己销毁时,才能将自己成员变量所指向的对象的引用计数减1。即:对象A的销毁依赖对象B的销毁,反之亦然。这样子就造成了循环引用吗。

  • 解决循环引用的方法

    1. 明确知道这里会存在循环引用,在合适的位置主动断开环中的一个引用,使对象得到回收。

    2. 使用弱引用,

/**
## 引用计数算法之GC
譬如有A和B两个对象,他们都互相引用,除此之外都没有任何对外的引用,那么理论上A和B都可以被作为垃圾回收掉,但实际如果采用引用计数算法,则A、B的引用计数都是1,并不满足被回收的条件,如果A和B之间的引用一直存在,那么就永远无法被回收了。
#由于Hotspot使用的是可达性分析算法,所以此示例循环引用的垃圾对象也可被回收
*/
//循环引用示例
public class GcDemo{
  
  public static void main(String[] args) {
      GcObject obj1 = new GcObject(); //Step1
      GcObject obj2 = new GcObject();//Step2
    
      obj1.instance = obj2; //Step3
      obj2.instance = obj1;// //Step4
    
      obj1 = null; //Step5
      obj2 = null; //Step6
  }
}

public class GcObject{
  public GcObject instance = null;
}

/**
采用引用计数算法时:

第一步:GcObject实例1被obj1引用,所以它的引用数+1,为1
第二步:GcObject实例2被obj2引用,所以它的引用数+1,为1
第三步:obj1的instance属性指向obj2,而obj2指向GcObject实例2,故GcObject实例2引用+1,为2
第四步:obj2的instance属性指向obj1,而obj1指向GcOjbect实例1,故GcObject实例1引用+1,为2
到此,发现GcObject实例1和实例2的计数引用都不为0,那么如果采用的引用计数算法的话,那么这两个实例所占的内存将得不到释放,这便产生了内存泄露。

      PS:注意想一下,为什么是obj的instance属性,而不是写成obj本身?如果写成obj, 如下:
      
      第五步:obj1不再指向GcOjbect实例1,则GcOjbect实例1的引用计数减1,结果为1.
      第六步:obj2不再指向GcOjbect实例2,则GcOjbect实例2引用计数减1,结果为1.    
*/

❸:

/**
 * @Description : 可达性分析方法的对象自我拯救演示
 * 此代码演示了两点:
 * 1.对象可以在被GC时自我拯救。
 * 2.这种自救的机会只有一次, 因为一个对象的finalize()方法最多只会被系统自动调用一次
 * @Author : Future Buddha
 * @Date: 2021-07-25 16:10
 */
public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed!");
        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 {
            isDead();
        }
        //下面这段代码与上面的完全相同,但是这次自救却失败了
        SAVE_HOOK = null;
        System.gc();
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            isDead();
        }
    }

    private void isAlive() {
        System.out.println("yes, i am still alive :)");
    }

    private static void isDead() {
        System.out.println("no, i am dead :(");
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黄土地的孩子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值