java 垃圾收集器《深入java虚拟机》笔记

在看垃圾收集器的这一章节时,开篇即提出了三个问题,我觉得看完这章节搞懂这三个问题大致也就明白此章内容了。

这三个问题分别如下:

1、哪些内存需要回收?

2、什么时候回收?

  2.1 引用计数算法

  2.2 可达性分析算法

  2.3 总结

3、如何回收?-- 垃圾收集算法

3.1 标记 - 清除算法 

3.2  复制算法

3.3 标记 - 整理算法

3.4 分代收集算法


下来会带着这三个问题看本章类容。

1、哪些内存需要回收?


在讲java内存区域章节中,得知内存运行时区域中的各部分,其中程序计数器,虚拟机栈,本地方法栈三个区域随线程而生,随线程而灭;栈中的栈帧随着方法的进去和退出有条不紊的进行着出栈和入栈的操作。每一个栈帧中分配多少内存基本上在类结构确定下来的时候就已知了。因此这几个区域的内存分配和回收都具备了确定性。所以不需要多做内存回收的考虑。

java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中多个分支需要的内存也可能不一样,我们只能在程序运行的时候才能知道会创建那些对象,这部分内存的分配和回收都是动态的,垃圾收集器所关注的是这部分的内存。

2、什么时候回收?


什么时候回收? 当然要在堆中的对象死了,或者存放在方法区的常量或者类没有用了的才会回收。

那么问题来了,如何判断堆中对象已死?

  2.1 引用计数算法

实现思路:在堆中存储对象时,在对象头处维护一个counter计数器,如果一个对象增加了一个引用与之相连,则将counter++。如果一个引用关系失效则counter--。如果一个对象的counter变为0,则说明该对象已经被废弃,不处于存活状态。

优点:实现简单,判断效率也高。

致命缺点:很难解决对象中相互循环引用的问题。这也是主流java虚拟机没有使用此算法管理内存的原因。

  2.2 可达性分析算法

实现思路:设定一系列称为“GC Roots” 的对象,以这些对象为起始点,向下开始检索,搜索走过的路径,称为引用链,当一个对象到“GC Roots” 对象的没有任何引用链的时候,证明此对象是不可达的。即认为是可以回收的。

优点:可解决相互循环引用的问题。

  2.3 总结

 无论上面的计数器算法还是可达性分析算法,判断存活的依据都是是否有引用。在JDK1.2 以后,Java对引用的概念进行了补充,将引用分为强引用,软引用,弱引用,虚引用。加入这些概念后,对象就不止是被引用和没有引用两种状态,这样对内存管理就可以做到更灵活一些。即便这些算法中没有被引用的对象,也并非是非死不可的。这个时候他们只是暂时放在了“缓刑”阶段。要真正宣告死亡,还要至少经历两次标记过程。如果对象被发现没有被引用后,那他将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。

当对象没有覆盖finalize()方法,或者finalize() 方法已经被虚拟机调用过,虚拟机将这两种情况都看作 "没有必要执行”。

当对象被判定为有必要执行finalize()方法, 那么这个对象将会放置在一个叫做F-Queue的队列之中。并在稍后由一个由虚拟机自动建立的,低优先级的Finalizer线程去执行它。这里所谓的执行事指虚拟机回去触发这个方法,但并不承诺会等待他运行结束,这之后GC将会对F-Queue 中的对象在做一次筛选,如果对象在finalize()与任何一个对象建立建立联系,就可以成功拯救自己。如下代码演示了Gc


/**
 * @Author: hecy
 * @Date: 2018/12/13 14:17
 * @Version 1.0
 */
public class Gctest {
    public static Gctest GC_HOOK = null;

    public void isActive(){
        System.out.println("我还活着,还可以抢救一下。");
    }
    @Override
    public void finalize() throws Throwable {
        super.finalize();
        System.out.println("完了,在不救我,我就死了。");
        Gctest.GC_HOOK = this;
    }


    public static void main(String []args) throws InterruptedException {
        GC_HOOK = new Gctest();
        GC_HOOK = null;
        System.gc();
        // 因为finalize() 方法优先级很低,所以暂定0.5s
        Thread.sleep(500);
        if(GC_HOOK != null) {
            GC_HOOK.isActive();
        } else {
            System.out.println("好吧,我死了");
        }

        GC_HOOK = null;
        System.gc();
        Thread.sleep(500);
        if(GC_HOOK != null) {
            GC_HOOK.isActive();
        } else {
            System.out.println("好吧,我死了");
        }

    }
}

 

运行结果如下:

在就,针对方法区的回收,方法区的回收主要回收两部分内容:废弃的常量和无用的类。对于常量,只要当前系统中没有任何对象引用此常量,也没有其他任何地方引用此字面量。即会被回收。

3、如何回收?-- 垃圾收集算法


既然知道了那些垃圾需要回收,就要着手清理回收工作了。

3.1 标记 - 清除算法 

实现思路:如同他的名字一样,算法分为标记和清除两个阶段,首先标记处所有需要回收的对象,在标记完成之后统一回收所有被标记的对象。

缺点: 

  • 效率问题, 标记和清除 效率都不高
  • 空间问题,标记清除之后会留下很多零碎的不连续的内存碎片。空间碎片太多会导致之后在程序运行过程中需要分配较大对象时,无法找到足够的连续的内存二不得不提前触发一次垃圾回收动作。

3.2  复制算法

实现思路:它将可用内存按容量划分为大小相等的两块,每次使用其中一块,当发成内存回收的时候,将其中还存活的对象复制到另外一块中,然后直接将已使用过的内存空间一次清理完成。

优点:清理效率提高,内存分配时也不用再考虑内存碎片的问题,实现简单。

缺点:也就是说每次可用内存都是实际内存的一半。资源浪费。(针对此,现在的商业虚拟机都不是按照1:1 比例来划分内存空间的。而是将内存划分成一块较大的Eden空间和两块Survivor空间,比例为8:1,每次都是直接将存活对象直接复制到其中一块Survivor中,然后清理其他两块。)

3.3 标记 - 整理算法

实现思路: 标记过程和 标记-清理算法相同, 但后续不是直接对可回收对象进行清理,而是让所有存活的对象都想一端移动,然后直接清理掉边界以外的内存。

3.4 分代收集算法

实现思路:根据对象存活周期的不同将内存划分为几块。一般是把java堆分为新生代和老年代,这样就可以对不同年代的对象实施不同的算法。在新生代中每次垃圾回收都会有大良的对象死去,只有少量存活,那就选用复制算法。只需要出少量复制成本就可以完成收集。而老年代中因为存活对象存活率高,没有额外空间对它进行分配担保,就必须使用标记-清除,或者标记 - 整理 算法。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值