美文网首页
GC垃圾回收算法

GC垃圾回收算法

作者: ArcherZang | 来源:发表于2019-11-13 21:01 被阅读0次

    https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)

    垃圾收集器

    垃圾回收器是算法的实现,通常是新生代和老年代使用不同的垃圾回收器组合来处理垃圾回收。

    1. 对象死亡算法

      • 引用计数算法
        给对象添加一个引用计数器,每当有一个地方引用它时计数器值加1;当引用失效时,计数器值减1;任何时刻计数器值为0时对象就不可能再被使用。
      Person a = new Person();
      Person b = new Person();
      a.instance = b;
      b.instance = a;
      a = null;
      b = null;
      System.gc();
      //对象被回收,如果引用计数就会永远不为0,导致无法回收。
      
      • 可达性分析算法
        通过一系列的"GC Roots"的对象作为起始点,从这些节点开始向下搜索,当一个对象与"GC Roots"没有任何引用链相连,证明此对象是不可用的。(也称GC Roots到这个对象不可达)。
        引用链:搜索所走过的路径
        GC Roots:虚拟机栈中的引用对象(栈帧中的本地变量表),
                           方法区中类静态属性引用的对象和常量引用的对象,
                           本地方法栈中JNI引用的对象(native方法)。


        gc root.png
    2. 垃圾回收算法

      • 标记清除算法
        首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,它的标记过程就是对象的可达性分析。
        不足点:标记和清除过程效率不高
        清除后产生大量不连续的内存碎片,可能导致分配大对象时无法找到足够的连续内存而触发另一次回收。

      • 复制算法
        内存按容量划分大小相等的两块,每次使用其中一块,回收时将存活的对象复制到另一块,然后将已使用的空间一次清理。
        不要考虑复杂情况,只要移动指针顺序分配内存,实现简单,运行高效。
        代价昂贵,一半的内存空间。
        分成三块,一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和一块Survivor空间,回收时将存活的对象复制到另一块Survivor空间,最后清理掉Eden和Survivor空间。默认8:1 Eden和Survivor,所有之后有10%的内存浪费,当Survivor空间不够用时,需要依赖老年代进行分配担保(Handle Promotion)

      • 标记---整理算法
        标记过程和标记清除算法一样,然后让所有存活的对象都向一端移动,让后直接清理掉端边界以外的内存

      • 分代收集算法
        把java堆分为新生代和老年代,根据各年代特点采用最适当的算法。
        新生代,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法。
        老年代,因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理或者标记-整理算法进行回收。
        大对象很容易提前触发垃圾回收,虚拟机提供-XX;PretenureSizeThreshold,大于这个值,对象直接在老年代分配,避免新生代发生大量的内存复制。
        虚拟机给每个对象定义了一个对象年龄计数器,如果对象出生在Eden并经过一次Minor GC后仍存活并且能够被Survivor容纳,就移动到Survivor空间中,并且对象年龄设置为1,在Survivor中每熬过一次Minor GC年龄就加1,年龄增加到阈值就会晋升到老年代,通过参数-XX:MaxTenuringThreshold设置,默认15。
        如果在Survivor空间中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象就可以直接进入老年代,无需等待年龄阈值。


        注意:Permanent和垃圾回收没什么关系,主要用来存放类,方法信息,也能作为常量沲使用,不同VM不同实现,有些没这个区
      • 回收方法区
        常量回收:没有任何地方引用这个字面量,清理出常量池。
        类回收:该类的所有实例都已经回收
        该类的classLoader已经被回收
        该类对应的java.lang.Class对象没有任何地方引用
        满足以上3个条件才能回收
        参数:-Xnoclassgc、-verose:class、-XX:+TraceClassLoading、-XX:+TraceClassUnLoading

    3. 空间分配担保
      在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,条件成立,那么MinorGC是安全的。如果不成立,虚拟机会查看HANDLE
      pROMOTINfAILURE设置值是否允许担保失败,如果允许那么继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于进行一次MinorGC,尽管有风险,如果小于或者不允许,改为进行一次Full GC。如果风险失败即Survivor无法容纳,就需要老年代担保,无法容纳的直接进入老年代,如果老年代无法容纳,就进行一次FullGC
      新生代GC(Minor GC):指发生在新生代的垃圾回收动作。回收速度比较快
      老年代GC(Major GC/ Full GC):指发生在老年代的垃圾回收动作,看垃圾回收器实现,有时会伴随Minor GC。Major GC的速度一般会比Minor GC慢10倍以上。

    4. 引用
      强引用:就是指在程序代码之中普遍存在的,类似”Object obj = new Object()“,只要强引用存在,垃圾回收器永远不会回收。
      软引用:是用来描述一些还有用但并非必须的对象。对软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。SoftReference类。
      弱引用:用来描述非必需对象的,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。
      虚引用:也称幽灵引用或者幻影引用,一个对象是否有虚引用存在对其生存时间没有影响,也无法通过虚引用获得对象实例,唯一目的在垃圾回收时收到一个系统通知,PhantomReference类

    对象
    1. 对象创建
      虚拟机遇到一个new指令,首先将去检查这个指令的参数能否在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程,类加载检查通过后,虚拟机为新生对象分配内存。对象所需内存大小在类加载完成时便可完全确定,把一块确定大小的内存从java堆上划分出来。虚拟机需要将分配到内存空间初始化为零值,接下来对对象进行必要的设置,主要是对象头设置,现在所有的字段都还为零,然后执行init方法,把对象按照程序员的意愿初始化。
      内存分配方式:
      指针碰撞:假设内存绝对规整,所有用过的内存都放到一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,分配内存就是把指针往空闲空间那边挪动一段与对象大小相等的距离。
      空闲列表:维护一个列表,记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。
      并发处理:
      对分配内存空间的动作进行同步处理---实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;
      内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。哪个线程要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。内存空间初始化为0可以提前到TLAB分配。
    2. 对象的内存布局
      对象头、实例数据、对齐填充
      对象头第一部分(Mark Word),用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、GC标志(空);
      第二部分时类型指针,即对象指向它的类元数据的指针和数组长度。
      实例数据:是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。
      存储顺序受虚拟机分配策略(FieldsAllocationStyle)和源码定义顺序(CompactFields)的影响
      虚拟机分配策略,longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers),相同宽度的字段总是被分配到一起
      可以让子类中较窄的变量插入到父类变量的空隙中
      对齐填充不是必然存在,HotSpot VM要求对象起始地址必须是8字节的整数倍,对象头正好8个字节,对象实例数据部分如果没有对齐时,就要通过对齐填充来补全。
    3. 对象访问定位
      java程序通过栈上的reference数据来操作堆上具体对象
      reference类型在虚拟机规范中只规定了一个指向对象的引用,所以对象的访问方式取决于虚拟机实现。
      使用句柄访问,java堆中划分出一块内存作为句柄池,reference中存储的就是对象的句柄地址,句柄池中存储堆上对象实例数据地址和方法区对象类型数据地址。
      垃圾回收时,不需要修改reference。
      使用指针直接访问,reference中存储的是堆上对象地址,对象分对象头和实例数据,对象头指针存方法区对象类型数据地址。
      垃圾回收时,要修改reference;对象访问快,节省一次指针定位。
    4. 对象死亡
      对象在进行可达性分析后发现没有与GC Roots相连的引用链,将会被第一次标记且进行一次筛选,筛选条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用过,视为不需要执行,即会被回收。需要执行finalize()方法的对象放在F-Queue的队列之中,并由虚拟机自动创建低优先级的Finalizer线程去执行它;只要在finalize()方法中重新建立与引用链的关联即可摆脱被回收,如果对F-Queue进行第二次小规模标记时没有建立关联,就会被移出并回收。
       public class Person() [
           public static Person SAVE_HOOK = null;
           protected void finalize() throws Throwable {
               super.finalize();
               Person.SAVE_HOOK  = this;
           }
       }
      
    5. 垃圾回收器
      一般都使用分代算法集成不同的垃圾回收器。


      image.png
      Serial串行收集器
      ParNew 收集器
      Parallel Scavenge收集器 吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。
      Serial Old收集器
      Parallel Old收集器
      CMS采用"标记-清理"算法实现以获取最短回收停顿时间为目标的收集器

      初始标记:标记一下GC Roots能直接关联到的对象
      并发标记:进行GC Roots Tracing 的过程
      重新标记:是为了修正并发标记期间因用户程序继续运行而导致标记产品变动的那一部分对象的标记记录
      并发清除:清除不能到达GC Roots的对象 
      重置线程:更新之前使用过的数据

    相关文章

      网友评论

          本文标题:GC垃圾回收算法

          本文链接:https://www.haomeiwen.com/subject/ghsnbctx.html