垃圾回收机制算法分析

                垃圾回收机制算法分析

什么是垃圾回收机制

          不定时去堆内存中清理不可达对象。不可达的对象并不会马上就会直接回收, 垃圾收集器在一个Java程序中的执行是自动的,不能强制执行,即使程序员能明确地判断出有一块内存已经无用了,是应该回收的,程序员也不能强制垃圾收集器回收该内存块。程序员唯一能做的就是通过调用System.gc 方法来"建议"执行垃圾收集器,但其是否可以执行,什么时候执行却都是不可知的。这也是垃圾收集器的最主要的缺点。当然相对于它给程序员带来的巨大方便性而言,这个缺点是瑕不掩瑜的

如何判断对象是否存活

  引用计数法 (淘汰)

    引用计数法就是如果一个对象没有被任何引用指向,则可视之为垃圾。这种方法的缺点就是不能检测到环的存在。

    首先需要声明,至少主流的Java虚拟机里面都没有选用引用计数算法来管理内存。 
    什么是引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值减1.任何时刻计数器值为0的对象就是不可能再被使用的。

    那为什么主流的Java虚拟机里面都没有选用这种算法呢?其中最主要的原因是它很难解决对象之间相互循环引用的问题。

  

  根搜索算法

    

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

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

    

    那么问题又来了,如何选取GCRoots对象呢?在Java语言中,可以作为GCRoots的对象包括下面几种:

        (1). 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。

        (2). 方法区中的类静态属性引用的对象。

        (3). 方法区中常量引用的对象。

        (4). 本地方法栈中JNI(Native方法)引用的对象。

  垃圾回收机制策略

      标记清除算法

        该算法有两个阶段。

               1. 标记阶段:找到所有可访问的对象,做个标记

            2. 清除阶段:遍历堆,把未被标记的对象回收

        应用场景

            该算法一般应用于老年代,因为老年代的对象生命周期比较长。

          1. 优点

            - 是可以解决循环引用的问题

            - 必要时才回收(内存不足时)

          2. 缺点:

            - 回收时,应用需要挂起,也就是stop the world。

            - 标记和清除的效率不高,尤其是要扫描的对象比较多的时候

            - 会造成内存碎片(会导致明明有内存空间,但是由于不连续,申请稍微大一些的对象无法做到),

    复制算法

      如果jvm使用了coping算法,一开始就会将可用内存分为两块,from域和to域, 每次只是使用from域,to域则空闲着。当from域内存不够了,开始执行GC操作,这个时候,会把from域存活的对象拷贝到to域,然后直接把from域进行内存清理。

     应用场景

      coping算法一般是使用在新生代中,因为新生代中的对象一般都是朝生夕死的,存活对象的数量并不多,这样使用coping算法进行拷贝时效率比较高。jvm将Heap 内存划分为新生代与老年代,又将新生代划分为Eden(伊甸园) 与2块Survivor Space(幸存者区) ,然后在Eden –>Survivor Space 以及From Survivor Space 与To Survivor Space 之间实行Copying 算法。 不过jvm在应用coping算法时,并不是把内存按照1:1来划分的,这样太浪费内存空间了。一般的jvm都是8:1。也即是说,Eden区:From区:To区域的比例是

始终有90%的空间是可以用来创建对象的,而剩下的10%用来存放回收后存活的对象。

    

      注意: 万一存活对象数量比较多,那么To域的内存可能不够存放,这个时候会借助老年代的空间。

      

    优缺点

      优点:在存活对象不多的情况下,性能高,能解决内存碎片和java垃圾回收算法之-标记清除 中导致的引用更新问题。

      缺点: 会造成一部分的内存浪费。不过可以根据实际情况,将内存块大小比例适当调整;如果存活对象的数量比较大,coping的性能会变得很差。

    标记压缩算法

  

    标记清除算法和标记压缩算法非常相同,但是标记压缩算法在标记清除算法之上解决内存碎片化

    

      压缩算法简单介绍

        任意顺序 : 即不考虑原先对象的排列顺序,也不考虑对象之间的引用关系,随意移动对象;

        线性顺序 : 考虑对象的引用关系,例如a对象引用了b对象,则尽可能将a和b移动到一块;

        滑动顺序 : 按照对象原来在堆中的顺序滑动到堆的一端

      

      优缺点

      优点:解决内存碎片问题,缺点压缩阶段,由于移动了可用对象,需要去更新引用

    

    分代算法

      

      概述

        这种算法,根据对象的存活周期的不同将内存划分成几块,新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。可以用抓重点的思路来理解这个算法。

        新生代对象朝生夕死,对象数量多,只要重点扫描这个区域,那么就可以大大提高垃圾收集的效率。另外老年代对象存储久,无需经常扫描老年代,避免扫描导致的开销。

    

      新生代

      在新生代,每次垃圾收集器都发现有大批对象死去,只有少量存活,采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;可以参看我之前写的java垃圾回收算法之-coping复制

      老年代

      而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须“标记-清除-压缩”算法进行回收。参看java垃圾回收算法之-标记_清除压缩

新创建的对象被分配在新生代,如果对象经过几次回收后仍然存活,那么就把这个对象划分到老年代。

老年代区存放Young区Survivor满后触发minor GC后仍然存活的对象,当Eden区满后会将存活的对象放入Survivor区域,如果Survivor区存不下这些对象,GC收集器就会将这些对象直接存放到Old区中,如果Survivor区中的对象足够老,也直接存放到Old区中。如果Old区满了,将会触发Full GC回收整个堆内存。

      

    

原文地址:https://www.cnblogs.com/zhouwen2017/p/9967606.html