欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

Java对象的"后事处理"——垃圾回收(二),垃圾回收处理设备

来源: javaer 分享于  点击 45352 次 点评:232

Java对象的"后事处理"——垃圾回收(二),垃圾回收处理设备


1 先谈Finalize()

finalize()能做的所有工作,使用try-finally或者其他方式都可以做得更好、更及时,所以笔者建议大家完全可以忘掉Java语言中有这个方法的存在。

——《深入理解JVM》

  finalize()方法确实可以实现一次对象的自救,但是其不确定性昂贵的运行代价都表明这个方法的使用需要十分的慎重。那么finalize()在什么时期起作用又是如何实现对象的自救的呢?首先我们要理解虚拟机在扫描到死亡对象的时候并不是直接回收的,而是进行一次标记并且筛选,筛选的条件就是其对象的finalize方法是否有必要执行。如果当前对象没有重写finalize方法或者已经调用过一次finalize方法,那么则视为没有必要执行,此时便失去自救的机会,放入"即将回收"集合中。

  否则的话,则将对象放入一个叫F-Queue的队列中,稍后虚拟机将一个个的执行队列中对象的finalize方法(就是在此处对象可以在finalize方法中将自身关联到引用链,从而暂时逃脱被回收的命运),需要注意的是虚拟机保证执行但不保证执行完finalize方法,原因是如果finalize方法执行时间过长或者陷入死循环,则可能让系统奔溃。全部执行之后,虚拟机将对队列的对象重新标记一次,如果还不在引用链中则GG,否则将其移出"即将回收"集合。下面例子参考《深入理解JVM》实现自救并且验证只能自救一次的过程。

public class TestForGc {

    /** 定义一个根节点的静态变量 */
    public static TestForGc INSTANCE;

    /**
     * 重写finalize方法,让其被标记为有必要执行并且加入F-Q
     *
     * @throws Throwable
     */
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.err.println("finalize method in TestForGc Class invoked!");
        // 将自身关联到根节点中,实现自救
        INSTANCE = this;
    }

    public static void main(String[] args) throws InterruptedException {
        INSTANCE = new TestForGc();

        INSTANCE = null;
        System.gc();
        // 睡眠1S,保证F-Q中的方法执行完毕
        TimeUnit.SECONDS.sleep(1);
        if (Objects.nonNull(INSTANCE)) {
            System.out.println("i successfully save myself by finalize method!");
        } else {
            System.out.println("i am dead :(");
        }

        /*
         * 下面验证finalize方法只能调用一次
         * 几乎完全一样的代码,却是不同的结局
         */
        INSTANCE = null;
        System.gc();
        // 睡眠1S
        TimeUnit.SECONDS.sleep(1);
        if (Objects.nonNull(INSTANCE)) {
            System.out.println("i successfully save myself by finalize method again!");
        } else {
            System.out.println("couldn't invoke finalize again, i am dead :(");
        }
    }
}
执行结果:

 2 垃圾回收器

  如果说回收算法是接口,那么垃圾回收器就是这些接口的实现类,共有7种回收器,接下来一一罗列。

2.1 Serial垃圾回收器

  Serial是一种单线程垃圾回收器,在工作的时候的时候会暂停所有的用户线程,也就是"stop-the-world",虽然单线程代表了用户线程的停顿,但是也意味着其不用进行线程的交互从而有更高的收集 效率。Serial采用复制算法,是Client端新生代的默认垃圾回收器。其工作图类似于:

-XX:MaxGCPauseMillis:表示收集器将尽可能的在这个参数设定的毫秒数内完成回收工作。但这并不代表其设置的越低越好,缩减回收时间是通过减少吞吐量换来的,如果设置得太低可能导致频繁的GC。

-XX:GCTimeRatio:表示代码运行时间和垃圾回收时间的比率,比如说设置为19,那么则垃圾回收时间占比为 1 / (1+19) = 5%,默认是99。

2.4 Serial Old垃圾回收器

  Serial的年老代版本,同Serial基本相似,不同的是采用的是标记-整理算法实现,作为Client端默认的年老代收集器。如果在Server端的话,那么其主要作用有二:

 1、跟新生代的Parallel Scavenge收集器配合。

 2、做一个有价值的"备胎":当CMS垃圾回收器因为预留空间问题放不下对象而发生Concurrent Mode Fail时,作为其备选方案执行垃圾回收。

 -XX:+UseCMSCompactAtFullCollection-XX:CMSFullGCsBeforeCompaction,分别表示CMS顶不住要进行FullGC的时候进行内存的整理(整理的过程中无法并发,停顿时间不得不变长) 和进行多少次不压缩的FullGC之后来一次整理的GC(默认0次,表示每次都进行内存整理)。

2.7 G1垃圾回收器

  G1是一个新秀垃圾回收器,被赋予了很大的使命——取代CMS。G1作为新时代的垃圾回收器,相对于其他垃圾回收器来说有许多优势。

1、并行和并发:G1可以利用现在的硬件优势,缩短GC时stop-the-world的停顿时间,并且GC的时候同时也能让用户线程执行。

2、分代收集:跟其他垃圾回收器不同,G1没有物理上的年老代和新生代,其将内存分成了多个独立的Region,每个Region都可能表示属于新生代还是年老代,所以不需要一堆Region凑放在一起然后将这块区域称作新生代,它们之间并不需要连续,所以只有概念上的分代,也是这种分代方式使得G1可以独立管理这个堆空间,不需要跟其他回收器合作。

3、空间整合:G1的算法从Region层面看属于复制算法(从一个Region复制到另一个),但是从整体看又是标记-整理法。然而不管是哪种,都表示G1不会产生内存碎片,不会因为空间不连续放不下大对象而出现FullGC的情况。

  G1回收器将内存空间分成若干个Region,并且这些Region之前相互独立。但是我们都知道这并不能真正的独立,因为一个Region中的对象不一定只会被当前Region的其他对象引用,而可能被堆中的其他对象引用,那G1是如何实现避免全堆扫描的呢?这个问题在分代的其他回收器中也有,但是在这里突显得更加明显而已。再G1中,对象本身都会有一个Remembered Set,这个Set存放着当前对象被其他区域对象引用的信息,这样子,在扫描引用的时候加上这个Set就可以避免全堆扫描了。

  具体实现大致为:虚拟机在发现程序正在进行对Reference类型的写操作时,会暂时中断写操作,然后检查Reference引用的对象是否处于不同的区域如果是分代,则只对年老代的对象进行检查,检查是否引用的对象在新生代),如果是的话则将引用信息记录在被引用的Remembered Set中,这样在GC的时候加上Remembered Set的扫描就可以避免全堆扫描了。

  跟CMS类型,G1也有四个阶段(不算Remembered Set的扫描),虽然相似但是还是有些区别的。

1、初始标记:标记可达的根节点,STW,单线程,时间短。

2、并发标记:跟用户线程同时执行,并发执行时对象可能会产生引用变化,其会将这些变化记录在Remembered Set Logs中,待下个阶段整合。

3、最终标记:验漏,将并发标记阶段的引用变化记录Remembered Set Logs整合到Remembered Set中。

4、筛选回收:对各个Region中的回收价值进行排序,然后执行回收计划。暂停用户线程,并行执行。 

It helps me a lot if you could share your opinion with us.

相关文章

    暂无相关文章
相关栏目:

用户点评