虚拟机垃圾回收过程

概述

垃圾回收是一块大内容,我们用三个问题来了解一下垃圾回收的过程

  • 什么是垃圾
  • 在什么时候回收垃圾
  • 怎么回收

什么是垃圾

判断堆内存中的对象是否是垃圾,我们有两种方法

引用计数算法

原理

在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时。计数器值就减一;任何时候计算器为零的对象就是不可能在被使用的。

优点

原理简单,判断效率高。

缺点

需要占用一些额外的内存空间,还有很难解决对象之间循环引用(两个对象相互引用)的问题。

解决办法

当循环引用的时候,可以手动去除掉互相引用

可达性分析算法

在这里插入图片描述

原理

这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

可作为GC Roots 的对象

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  2. 方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
  3. 方法区中常量引用的对象,譬如字符串常量池里的引用。
  4. 本地方法栈中JNI(即一般说的Native方法)引用的对象。
  5. java虚拟机内部引用,如基本数据类型对应的Class对象,一些常驻的异常对象等
  6. 所有被同步锁持有的对象。

在什么时候回收

判断步骤

  1. 如果对象到GCRoots没有引用链,则进行第一次标记。
  2. 进行筛选判断对象是否有必要执行finalize方法。
  3. 如果对象没有重写finalize方法或者finalize方法已经被调用过则没有必要在调finalize方法。

例子

/**
 * 标记为垃圾之后,会尝试一次自救finalize,如果重新复制则不会被回收,如果第二次在出现GC,则不会在执行finalize
 */
public class FinalizeTestGC {

    private static FinalizeTestGC testGC = new FinalizeTestGC();

    public static void main(String[] args) throws InterruptedException {
        //复制为null,已经没有引用了
        testGC = null;
        System.gc();
        Thread.sleep(2000);
        testGC.test();
        testGC = null;
        System.gc();
        testGC.test();
    }

    private void test(){
        System.out.println("成功跳脱GC");
    }

    @Override
    protected void finalize(){
        System.out.println("开始自救");
        testGC = this;
        System.out.println("自求结束");
    }
}

执行结果
在这里插入图片描述
从结果可以看出,第一次赋值为空,进行垃圾回收,程序运行了finalize方法,重新赋上值;第二次为空时,进行垃圾回收就不会在走finalize方法了,这也说明了finalize方法只能调一次,最后就没垃圾回收了。

怎么回收

各种 GC 算法在删除不可达对象时略有不同, 但总体可分为三类: 清除( sweeping)、整理( compacting)和复制( copying)

经典算法

标记-清除算法

首先标记出所有需要回收的对象,在标记完成后,统一的回收掉被标记的对象,也可以反着来。
缺点:执行效率不稳定,内存空间碎片化问题

标记-整理算法

其中的标记阶段是跟标记清除算法一样的,但是后续步骤不是对对象进行直接回收,而是让所有存活的对象都向内存空间一端移动,然后直接清除掉边界以外的对象。
优点:吞吐量提高了
缺点:移动存活对象并更新对象引用会暂停用户线程(stop the world)

标记-复制算法

它将可用的内存分成两块,每次只使用一块内存,当这一块内存用完后就将还存活的对象复制到另一块内存中,然后把已使用的内存空间一次性清除掉。
例如:我们新生代就分成Eden和Survivor比例为8:1。
优点:执行效率高,没有碎片化问题
缺点:需要浪费内存,当存活对象太多时不太适合

衍生算法

分代算法

将堆分成不同的区域,一般至少分成新生代和老年代,然后先将对象放在新生代,进行一次回收,新生代使用标记复制算法,将存活的对象复制到Survivor区,然后进行第二次回收,将Eden和Survivor区的存活对象复制到空闲的Survivor区,以此类推,当对象达到一定年龄(回收次数)的时候,就将对象复制到老年代,老年代早期进行标记清除算法,当老年代内存空间出现碎片化过多时进行标记整理算法回收。
优点:回收效率比较高,空间利用率比较好,由于将堆分成了几个部分,回收时不需要扫描整个堆空间,提高了吞吐量
问题:跨代引用问题?新生代可能被老年代引用,但是我们又不能扫描整个老年代
解决办法:只需要在新生代上建立一个全局的数据结构(记忆集),这个结构把老年代分成若干小块,表示出老年代的那一块内存会存在跨代引用。此后当发生MinorGC时,只有包含了跨代引用的小块内存里的对象才会被加入到GCRoots进行扫描。

增量算法

用户线程与垃圾收集线程并发处理, 它并不会等GC执行完, 才将控制权交回程序, 而是一步一步执行, 跑一点, 再跑一点, 逐步完成垃圾回收, 在程序运行中穿插进行. 极大地降低了GC的最大暂停时间.
优点:提高了响应速度

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值