• JVM垃圾收集规则和算法


    1.垃圾收集 Garbage Collection

    • 程序计数器、虚拟机栈、本地方法栈这三部分内存随着线程生而生,随着线程灭而自然的回收,他们的大小在编译期间就大致确定了下来,所以对这部分的回收是具备确定性的。
    • Java堆,方法区则不一样,在运行期间会创建对象,对象内存分配和回收都是动态的,所以是不确定的,具备随机性。

    2.判断对象已死

    1. 引用计数法: 有一个地方引用它就加1,引用失效就减1,为0就进行回收,但有一缺点就是无法解决对象间循环引用问题。Python使用了引用计数法,主流的jvm都没使用引用计数法。
    2. 可达性分析: 通过GC Roots的对象作为起点向下搜索,经过的路径叫做 引用链。当一个对象通过引用链到达不了,则这个对象不可达,将会被回收。
      1. 因为主要GC在堆和方法区工作,主要清理这部分的对象。对象是由线程启动之后创建的,随着线程退出一些对象就不可用了,线程退出意味着方法的退出,而方法和jvm栈、方法区、本地方法栈都有关系,因此要从这些地方开始向下搜索引用链,因此GC Roots对象包括以下几种。
        • 虚拟机栈中引用的对象,就是那些在栈帧的本地变量表中引用的对象
        • 方法区中类成员引用的对象
        • 方法区中常量引用的对象。
        • 本地方法栈中JNI引用的对象,即Native方法中引用的对象。

     3.引用

    1. 强引用,就是一般的引用。
    2. 软引用,SoftReference类来实现。在将要发生内存溢出前回收。
    3. 弱引用,WeakReference类来实现,只能存活到下一次垃圾回收前,下一次垃圾回收时被回收。
    4. 虚引用,也称幽灵引用,回收对象时的一个状态,该对象肯定被回收,作用就是给系统一个通知我要被回收了。

    4.不可达对象与回收

    • 不可达对象不一定会被回收,在回收之前至少要经历2次标记过程。
    1. 发现对象不可达标记一次,并进行一个筛选。
    2. 筛选是为了选出覆写了finalize()方法且该方法还未被jvm调用的对象,并把它们放入一个F-Queue队列中,该队列就是用来执行finalize方法的,并且由一个低优先级的Finalizer线程执行finalize()方法,但不保证finalize()方法执行完,只保证每个对象执行一次finalize()方法。
    3. 如果没覆写或者覆写了但调用了则不会被放入F-Queue队列中,因为finalize方法只执行一次。
    4. 自救:在finalize方法中如果重新将本身(this)赋值给某个类变量和某个类的成员变量则被第二次标记时会被移出“即将回收集合”,finalize方法时对象自救的唯一机会。
    5. 此时对在F-Queue队列中未能自救成功的对象进行第二次标记,被标记2次意味着这个对象即将死亡。
    6. 因为finalize方法肯定会执行一次所以可利用此特性做一些操作,但是不建议这么做,因为他不一定能执行完,它可以做的try-finally也可以做。

     5.回收方法区

    • jvm规范不要求方法区一定实现垃圾回收,但是也是存在垃圾收集,但是效率太低因为只回收废弃常量和无用类(进行卸载类),但回收这些可以被可以回收的对象性价比太低,还不如不回收。
    1. 回收常量和回收对象类似。
    2. 回收类(Class对象)即卸载类比较苛刻
      1. Java堆中不存在任何实例。
      2. 加载类的classLoader 已经被回收
      3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
    3. 即使满足这三个条件也不一定被回收,只是具备了被回收的可能。
    4. Hotspot 提供了参数来控制是否被回收,在大量使用反射 动态代理 等等情况下会生成大量类,此时就用参数要控制进行卸载类,保证方法区不会溢出。
    5. JVM提供了几个参数控制类回收:

      • -Xnoclassgc:关闭CLASS的垃圾回收功能
      • -verbose:class:在控制台查看类加载情况
      • -XX:+TraceClassLoading:查看类加载信息
      • -XX:+TraceClassUnLoading : 查看类卸载信息(FastDebug版的虚拟机才支持)

    6.垃圾回收算法

    1. 标记-清除算法 Mark-Sweep
      • 先标记出需要清理的对象然后再统一回收。这是最基础的算法,其他都是他的改进型。
      • 不足之处: 标记和回收效率都不高,而且清理之后会产生大量的不连续的内存碎片,一旦要分配一个大内存对象时不得不提前触发一次垃圾收集。
    2. 复制算法: Copy
      • 将内存分为大小相等的部分,每次只使用一块,满了之后将存活对象复制到另一块,再清除之前那块。
      • 如果将内存分为大小相等的2部分,那么每次只能使用一部分代价太大。
      • 商业虚拟机都是使用这种算法来回收 新生代,新生代中98%的对象存活时间非常短,所以并没有按照1:1来分割,而是按照8:1:1分割成1块Eden和2块Survivor,新生代可使用区是Eden和一块Survivor,这样只有10%空闲。
      • 每次将Eden和其中一块Survivor中存活的对象复制到另一块上,如果Survivor不够用那么会将存活对象分配到老年代上。
      • Survivor存在的作用就是减少向老年代分配的对象,如果没有Survivor那么每次GC都要把存活对象放入老年代,此时新生代空空如也而老年代很快就会满,从而触发Full GC 更加耗费时间。
      • 如果1块Survivor那么会将Eden存活的对象复制到Survivor上,Survivor上存活的对象还在原处,这样Survivor就存在了碎片,如果是2块Survivor那Eden和其中一块Survivor中存活的对象移到另一块上就不会存在碎片。
    3. 标记-整理
      • 复制算法在存活对象比较多的情况下效率就会减低所以它适合新生代,不适合老年代。
      • 标记后先不对可回收对象进行回收而是让存活对象向一端移动,然后清理边界以外的内存。
      • 由于老年代存活对象多,所以移动的概率小反而效率高,如果存活对象少那么移动的代价就大了。
      • 标记-整理算法实现和优化
    4. 分代收集
      • Java把堆分为新生代和老年代,新生代中有大量对象死去所以适合复制算法,老年代有大量对象存活所以适合标记-清理,标记-整理进行回收。

    7.HotSpot算法实现

    1. 枚举根节点

      1. 要从GC Roots节点开始查找引用链就得确定哪些引用(Reference对象)可以作为GC Roots,而存在可以作为GC Roots 的内存区域有很多其他类型数据,逐个检查势必耗费大量时间。
      2. HotSpot中用OopMap的数据结构来存放引用信息,避免检查全部数据。
    2.  Stop The World

      1. 要确定GC Roots 首先要让系统停顿,不能出现引用还在变化的情况,否则可达性分析(确定GC Roots 和查找引用链)结果准确性无法得到保证,这点导致GC进行时必须停止所有Java线程的一个重要原因,所以又称 Stop The World。即使号称不会发生停顿的GMS收集器中枚举根节点时也会发生 停顿。
      2. 停顿下来并不需要一个不漏的检查引用,因为虚拟机知道哪些地方存放着对象引用。
      3. HotSpot中用OopMap的数据结构来存放“什么地方有引用”的信息,在类加载过程中及以后的JIT编译过程中执行到特定位置时会计算出引用信息保存到对应OopMap中。
      4. 特定位置就是 安全点
    3. 安全点 Safepoint

      1. HotSpot并不需要为每一个指令生成信息保存在OopMap中,而是在特定位置计算出 引用信息 并保存到OopMap中,这个特定位置就是安全点。
      2. 程序执行时并不是任何位置都可以停下来GC,而是到达安全点才可以暂停。
      3. 安全点的特征就是之后可能程序会长时间执行,如方法调用,循环跳转,异常跳转,这些指令就会产生安全点。
      4. 要想GC就要让所有线程都到达安全点,这有2种方式实现
        1. 抢断式中断: 主动中断所有线程,发现中断点不是安全点则恢复线程,让他跑到安全点。 几乎没有虚拟机使用抢断式中断。
        2. 主动中断: 给每个线程设置一个中断标志,中断标志就在安全点处,线程发现中断标志位true则挂起线程
    4. 安全区 Safe Region

      1. 如果线程处于sleep或者blocked,那么他就无法走到安全点,GC时会标记一个 Safe Region ,在此区域任何地方开始GC都是安全的,当线程被唤醒继续执行进入Safe Region再离开时会检查GC是否完成,如果完成则离开,如果没有完成则暂时不离开安全区。

    8.垃圾收集器

    9.内存分配和回收

  • 相关阅读:
    Jvm年轻代复制到Survivor To区时,对象存放不下会发生什么?
    Jvm内存布局和Java对象内存布局
    ArrayList的removeIf和iterator.remove性能比较
    闲着没事做,用js做了一个冒泡排序的动画
    对象与this
    idea 简记
    线程按序交替
    大数阶乘
    序列化 与 反序列化
    人月神话
  • 原文地址:https://www.cnblogs.com/mibloom/p/9603763.html
Copyright © 2020-2023  润新知