zoukankan      html  css  js  c++  java
  • 《Understanding the JVM》读书笔记之二——垃圾回收算法

    垃圾收集器工作的第一步就是判断对象是否还活着,通过垃圾回收算法判断。

    一、引用计数算法
      • 在对象A中添加一个引用计数器,当有一个地方引用A时,计数器+1;当引用失效时,计数器-1,任何时刻计数器数值为0时,这个对象就不会再被使用了;
      • 引用计数法的实现简单,判断效率高。但再主流的java虚拟机中没有使用此算法,原因是,它无法解决相互循环引用问题。

    二、可达性分析算法:(不可达意味着对象死亡的可能性高)
      • 通过一系列GC roots 对象作为起始点,从GC Roots开始向下搜索,所走过的路径称为 引用链,当一个对象到GC Root没有任何引用链时,则证明此对象是不可用的。
      • Java中可以被看作GC Root的对象包括:
        a. 虚拟机栈中引用的对象(栈帧中的局部变量表)
        b. 方法区中类静态属性引用的对象
        c. 方法区中常量引用的对象
        d. 本地方法栈中JNI(Native)方法引用的对象。

    三、生存还是死亡?(即使是不可达的对象,也不是非死不可)
      1. 查找引用链
      • 当对象在可达性分析后发现没有与GC Root相连的引用链,此对象会被标记(第一次),并进行一次筛选。
      2. 第一次筛选
      • 筛选的条件:此对象是否需要执行finalize()方法,当此对象没有覆盖finalize()方法或finalize()方法已经被调用过=>没有必要执行,放入被会收集和;否则==>有必要执行;
      3. 放入队列
      • 如果判定有必要执行finalize,此对象会先被放到F-Queue队列中,
      4. Finalizer线程
      • 稍后,虚拟机会自动建立Finalizer线程,对F-Queue中的对象触发finalize()方法,但finalize()方法并不一定会执行完毕。(原因:如果finalize()方法执行缓慢、或死循环将导致整个队列等待)
      5. 第二次标记
      • 再稍后,GC将对F-Queue中的对象进行第二次标记(逃脱死亡的最后一次机会),标记原则:如果此对象再finalize()方法中与GC Root引用链上的任何对象建立关联==>移出”被回收对象集合“;否则==>不移出。
      6. GC回收

    四、方法区是否有必要回收
      • 相比之下,新生代中GC回收效率较高,常规应用一次GC可以回收70~90%的空间;
      • 永久代(方法区)回收性价比较低,主要回收:废弃常量、无用的类两种。而在jdk1.7以后,常量池已经移至堆内存,这部分也不应该计算在内。
      • 无用的类需要满足以下三个条件:
        a. 该类所有实例都被回收
        b. 加载该类的ClassLoader已经被回收
        c. 该类对应的java.lang.Class对象没有在任何地方引用(没有反射创建类)

    五、常见算法实现
    标记-清除算法:
      1. 对所有要回收的对象进行标记(见生存还是死亡)
      2. 回收被标记的对象
      3. 算法问题:
        • 效率:标记和清除效率都不高
        • 空间:清除后会产生大量内存碎片,在之后分配大对象时无法找到足够的连续内存而提前触发第二次GC

    复制算法:
      1. 将内存容量划分为大小相等的两块,每次只是用其中的一块,
      2. 当一块内存使用满,将还存活着的对象复制到另一块内存,
      3. 一次性清除已满的内存块。
      4. 算法问题:
        • 对整个区域进行回收,不需要考虑内存碎片问题,而且指针按顺序移动,效率高。
        • 但代价是可用内存减少到一半
        • 在对象存活率较高的情况下需要进行较多的复制操作,效率会变低
      5. 现代商业虚拟机都采用这种算法回收新生代,但比例不是1:1,因为新生代对象有98%左右朝生夕死:
        • HotSpot虚拟机将新生代内存划分为一块较大的Eden区和两个较小的Survivor区,E,S比例为8:2。
        • 每次使用其中的Eden区和一个Survivor区,当GC回收时,将所有存活的对象复制到另一个Suivivor区中,这样只有10%的内存被“浪费”。
        • 而当另外的Survivor区内存不够分配时,需要依赖其他内存进行担保(老年代)

    标记-整理算法:
      1. 标记过程同 标记-清除
      2. 标记后让所有对象都向一端移动,然后直接清理掉边界以外的内存
      3. 主要用于老年代的回收

    分代收集算法:
      • 当前商业虚拟机都使用分代收集:将所有对象按照生命周期,将内存划分为几块,一般划分为:新生代和老年代。
      • 对于新生代,每次GC都会回收大量对象,使用复制算法
      • 对于老年代,对象存活率较高,不需使用标记-清除或标记-整理算法来回收

    六、HotSpot虚拟机算法实现:
    1. 枚举根节点
      • 在可达性分析中,GC Root节点主要在全局引用和执行上下文中,在大型应用中(方法区几百兆)必然耗费很多时间,另外,为了确保一致性,在可达性分析的过程中,整个系统必须停顿(stop the world),即使在CMS收集器(号称不会停顿)也必然产生停顿。
      • 大部分Java虚拟机都是用准确式GC,虚拟机能够知道那些地方存储着对象引用(HotSpot使用OopMap数据结构来实现),防止全局扫描。
      • 在HotSpot中,类加载时就把对象什么偏移量上对应的数据类型计算出来;在编译过程中,也会在特定位置记录下栈和寄存器中哪些位置是引用,这样GC扫描时就可以直接得知。
    2. 安全点
      • 特定位置称为安全点==>程序执行时只有在到达安全点时才能开始GC。这就需要考虑两个问题:
      • Safepoint即不能太少也不能太多,太少会让GC等待时间过长,太多会增大运行时的负荷。
      • 如何在GC发生时,让所有线程都停在最近的安全点上。主要有抢先式中断和主动式中断两种方式。
      • 抢先式中断==>在GC时首先把所有线程强制中断,如果发现有没到达安全点的线程,就恢复该线程,让它“跑”到安全点上。现在几乎没有虚拟机使用
      • 主动式中断==>当需要GC时,不主动中断线程,只设置一个标志,各个线程主动轮询这个标志,发现中断标志为真时,线程自己中断挂起。而轮询标志的地方是安全点重合+创建对象需要分配内存的地方
    3. 安全区域
      • 为了应对线程不执行的时候(没有抢占到CPU时间或线程sleep),无法响应线程中断的请求,线程也就无法暂停的问题,引入了安全区。
      • 安全区是在一段代码片段中,引用关系不会发生变化,在这个区域中的任意地方开始GC都是安全的。
      • 在GC时不用管自己标识为SafeRegion的线程了,在线程要离开SafeRegion时,需要检查系统是否已经完成了根节点枚举,完成则继续执行,否则等待收到安全离开SafeRegion的信号为止。

  • 相关阅读:
    子类调用父类被重写的方法
    Linux下编译出现undefined reference to ‘pthread_create’问题解决
    CRC校验8
    嵌入式C语言查表法
    Static关键字,遇到的问题_1
    java中方法的参数传递机制_一个对象被当作参数传递到一个方法后
    String使用equals和==比较的区别
    如何导入XML数据 (python3.6.6区别于python2 环境)
    0xx_PHP核心01
    PHP_MVC设计模式02
  • 原文地址:https://www.cnblogs.com/logic-hatten/p/11321476.html
Copyright © 2011-2022 走看看