JVM---GC

 

概述

/**
     *  【GC---概述】
     *      <什么是垃圾>
     *          在运行程序中 没有任何指针指向的 对象;
     *
     *      <为什么要进行GC>
     *          a, 内存消耗完
     *          b, 碎片整理
     *              除了回收垃圾对象外,GC也可以清理内存的记录碎片,碎片整理将整理出的内存分配给新的对象;
     *          c, 影响程序正常运行
     *              随着应用程序业务越复杂,用户越来越多,没有GC就不能保证应用程序的正常运行;
     *              经常造成的STW的GC跟不上实际的需求,需要对GC进行优化;
     *
     *      <早期的GC>
     *          在早期的C/C++时代,GC基本是是手工进行;
     *          开发人员可以使用new申请内存,delete释放内存;
     *
     *          特点:
     *              1、灵活控制内存
     *              2、给开发人员带来 频繁申请和释放内存的管理负担
     *                  若有一处内存区由于开发人员忘记,就会产生内存泄漏,垃圾对象永远无法清除;
     *
     *      <Java垃圾回收机制>
     *          自动内存管理,无需开发人员手动参与内存的分配与回收;
     *
     *          特点:
     *              1、降低内存泄漏、内存溢出的风险;
     *              2、开发人员从繁重的内存管理释放出来,专注于业务开发
     *              3、弱化开发人员在程序出现异常时定位、解决问题的能力;
     *
     *          垃圾回收器 对堆(回收重点)、方法区进行回收;
     */

  

标记阶段

/**
     *  【GC---垃圾回收-标记阶段】
     *
     *          <标记阶段做什么?>
     *              堆中存放几乎所有的Java对象实例,在GC之前,首先需要判断内存中哪些是存活对象,哪些是死亡对象;
     *                  (只有被标记为死亡对象,GC在执行垃圾回收时,才会释放其占用的内存空间)
     *
     *          <如何判断一个对象已经死亡?>
     *              当一个对象 不被任何其他对象引用;
     *
     *          <标记阶段算法>
     *              1、引用计数算法
     *                  对每个对象保存一个整型的 引用计数器属性;
     *
     *                  引用计数器:
     *                      作用:
     *                          用于记录对象被引用的情况;
     *
     *                      对于一个对象A,只有任何一个对象引用A,则A的计数器+1;
     *                                              当引用失效时,A的计数器-1;
     *                                              当A的计数器值为0,表示A不再被使用;
     *
     *                  优点:
     *                      实现简单,垃圾对象易于标识;
     *
     *                  缺点:
     *                     需要单独的字段存储计数器,增加内存空间的开销;
     *                     每次赋值都要更新计数器,增加了时间开销;
     *                     无法处理循环引用的问题(导致Java的垃圾回收没有选择引用计数算法);
     *                        eg:
     *                            p -> A <-> B
     *                                rc=2  rc=1
     *
     *                            当P引用移除后,导致内存泄漏
     *                                A <-> B
     *                               rc=1  rc=1
     *
     *              2、可达性分析(根搜索、追踪性垃圾收集)算法
     *
     *                  GC Roots:
     *                      一组必须活跃的引用;
     *
     *                      Java中,GC Roots包括哪些元素:
     *                          虚拟机栈内
     *                              参数,局部变量等;
     *                          本地方法栈内
     *                          static变量
     *                          ...
     *
     *                  引用链:
     *                      可达性分析算法,内存中存活对象 会被 GC Roots直接或间接连接着,搜索所走过的路径称为 引用链;
     *
     *                  基本思路:
     *                      以GC Roots为起始点,按照从上至下的方式搜索 被GC Roots所连接的目标对象是否可达;
     *                      如果目标对象没有任何 引用链 相连,则不可达,意味着目标对象死亡,可标记为垃圾;
     *
     *                  注意:
     *                      当使用 可达性分析算法 判断内存是否可回收时,分析工作必须在 一个能保障一致性的快照中 进行;
     *                      GC时 必须Stop The World就是这个原因;
     */

 

 

对象的finalization机制

/**
     *  【GC---对象的finalization机制】
     *             Java语言 提供了 对象终止(finalization)机制 允许开发人员 提供 对象被销毁之前的自定义处理逻辑;
     *
     *              finalize():
     *                  java.lang.Object#finalize()
     *
     *                  Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. 当某个对象为垃圾时,垃圾收集器会调用该对象的finalize();
     *                  A subclass overrides the finalize method to dispose of system resources or to perform other cleanup. 子类可以重写finalize() ,用来处理系统资源或执行其他清理;
     *
     *                  ***只会被 垃圾收集器 调用一次;
     *
     *              注意:
     *                  永远不要显式调用某个对象的finalize(),应该交给 垃圾收集器去调用,理由:
     *                      1、可能导致对象复活
     *                      2、执行时间没有保障,完全由GC线程决定;
     *                      3、
     *
     *              由于finalize()的存在,虚拟机中的对象一般处于3种可能状态:
     *                  可触及:
     *                      从根节点开始,可以到达这个对象;
     *                  可复活:
     *                      从根节点开始,所有引用都被释放,但是有可能在finalize()中复活;
     *                  不可触及:
     *                      对象的finalize()被调用 且 没有复活;
     *
     *              判断一个对象是否可回收,至少经历2次标记过程:
     *                  1、如果对象A到GC Roots不可达,标记第一次;
     *                  2、判断对象A是否有必要执行finalize():
     *                      a, 如果对象A没有重写finalize() 或 finalize()已被调用,对象A 为不可触及;
     *                      b, 如果对象A重写finalize() 且 未被调用,对象A会被插入到 队列中,由低优先级的Finalizer线程 调用对象A的finalize();
     *                          如果调用finalize()后,对象A可达,则被移出 被回收 集合;
     */

  

public class FinalizeTest {

    static Object o;

    @Override
    protected void finalize() throws Throwable {
        System.out.println("sub finalize...");
        o = this;
    }

    public static void main(String[] args) throws InterruptedException {
        o = new FinalizeTest();
        o = null;
        System.gc();

        Thread.sleep(10000);

        if (null == o){
            System.out.println("o is dead");
        }
        else {
            System.out.println("o is still alive");
        }
    }
}

 

使用jprofiler查看GC Roots、分析OOM

/**
     *  【GC---使用jprofiler查看GC Roots、分析OOM】
     *      jprofiler
     *          Idea插件
     *
     *      dump文件
     *          what
     *              进程的内存镜像;
     */

  

清除阶段

/**
     *  【GC---垃圾回收-清除阶段】
     *      当成功标记出内存中存活对象和死亡对象后,GC接下来的任务就是执行垃圾回收,释放死亡对象所占用的内存空间,以便有足够的可用空间为新对象分配内存;
     *
     *      <清除阶段算法>
     *          1、标记-清除算法 Mark-Sweep
     *              实现思路:
     *                  当堆中有效内存空间被耗尽时,会STW,然后进行2个操作:
     *                      标记:
     *                          垃圾回收器 从引用根节点开始遍历,标记 所有被引用的对象(在对象的Header中记录为可达对象);
     *                      清除:
     *                          垃圾回收器 对堆内存 从头到尾 进行线性遍历,如果发现某个对象在其Header中没有标记为可达对象,则进行回收;
     *
     *              缺点:
     *                 效率不高;
     *                 GC的时候,需要STW,导致用户体验很差;
     *                 这种方式清理出来的内存是不连续的,容易产生碎片,需要维护一个空闲列表;
     *
     *              注意:
     *                  何为清除?
     *                      清除并不是真的置空,而是把需要清除的对象地址保存在空闲列表中;
     *                      当有新对象需要加载时,判断垃圾位置空间是否够,如果够,就覆盖垃圾空间内容;
     *
     *          2、复制算法 Copying
     *              实现思路:
     *                  将内存空间分为2块,每次只使用其中一块;
     *                  在GC时,将 可达对象 复制到 另一个块内存中,然后清除正在使用的内存;
     *
     *              优点:
     *                  没有标记、清除过程,实现简单,运行高效;
     *                  复制后的内存空间连续,不会出现碎片问题;
     *
     *              缺点:
     *                  需要2倍的内存空间;
     *                  复制算法需要维护引用关系,时间开销也不小;
     *                  适用于 可达对象比较少的场景;
     *
     *              场景:
     *                  新生代的 Survivor区
     *
     *
     *          3、标记-压缩(整理)算法 Mark-Compact
     *              实现思路:
     *                  标记:
     *                      垃圾回收器 从引用根节点开始遍历,标记 所有被引用的对象(在对象的Header中记录为可达对象);
     *                  压缩:
     *                      移动所有可达对象 且 按内存地址依次排列,然后,将末端内存地址以后的内存全部回收,无需使用空闲列表;
     *
     *              优点:
     *                  解决 标记-清除算法 的碎片化问题;
     *                  解决 复制算法 的内存减半的问题;
     *
     *              缺点:
     *                  移动对象同时,需要维护引用关系;
     *                  移动过程中,需要STW;
     *
     *              本质:
     *                  标记-清除算法执行后,再进行一次内存碎片整理;
     *
     *              标记-清除 与 标记-压缩 的区别:
     *                  标记-清除 是非移动式的回收算法;
     *                  标记-压缩 是移动式的回收算法;      
     */

  

标记-清除算法

 

 复制算法

 

 标记-压缩算法

 

 

posted on 2022-05-06 16:56  anpeiyong  阅读(27)  评论(0编辑  收藏  举报

导航