• Java垃圾回收机制


    判断对象是否存活

    引用计数法
    1. 实现方式:为每个对象创建一个私有的引用计数器,当引用计数器为0时,标记为可被回收对象,
    2. 存在问题:无法解决对象之间相互引用的问题。当两个可被回收对象彼此引用时,引用计数器永远不为0,极易发生内存泄露
    可达性分析法(Reachability Analysis)/根追踪算法(GC Roots Tracing)
    实现方式:通过一系列称为“GC Roots”的对象作为起始点,沿着引用链索搜索对象;当GC Roots到某个对象不可达时,证明该对象可被回收;
    使用该算法后,内存中的存活对象都会和GC Roots在一个连通分量中;
    当一个对象被可达性分析法判定为不可达对象后,该对象会被第一次标记,然后根据是否需要调用finalize()方法决定是否最终回收;
    • 当对象没有重写finalize()方法或者对象的finalize()方法已被调用过(finalize()方法至多被调用一次),虚拟机判定可回收并最终回收;
    • 当对象调用finalize()方法并在执行过程中,与引用链上的任何对象建立关联,虚拟机将其移除出“即将回收”集合;
    在HotSpot中,可作为GC Roots的对象包括:
    • 栈中引用对象(栈帧中的本地变量表);
    • 方法区中static成员引用的对象;
    • 方法区中常量引用的对象;
    • 本地方法栈中Native方法引用的对象;
    • 与一个类对象的唯一的Class对象;

    垃圾收集算法(分代收集算法)

    为什么分代

    • 分代原因:不同对象的生命周期时不一样的,因此对不同周期的对象采取对应的收集方式,提高回收效率;
    • 不分代:如果不进行分代,每次垃圾回收都将对整个堆进行回收,花费实际较长;

    分代收集算法

    分带收集算法根据对象存活周期的不同将堆区内存划分为新生区,养老区和永久区,然后根据各自特点分别采用最合适GC算法,当前主流虚拟机的GC都采用分代收集算法;

    标记-清除算法复制算法标记-整理算法
    实现标记出全部需要回收的对象,最后统一清理所有被标记对象
    将当前内存区域内的存活对象全部复杂到另一片内存区,然后请理全部内存空间首先标记出全部需要回收的对象,然后将所剩余的所有存活对象都移动到一端,最后直接清理边界外的所有内存;
    指针碰撞:执行整理后,已用内存和未用内存各自一边,维护一个分界指针,当新对象分配内存时,使用指针碰撞技术将新对象分配在第一个空闲内存位置上;
    不足效率低,
    造成内存碎片,不利于内存分配
    在对象存活率较高时,复制操作频繁,效率较低;

    应用适用于CMS收集器进行Full GC新生区中进行Minor GC主要采用
    不适用于养老区GC
    在标记-清除算法上改进,有效避免内存碎片
    适用于Serial, ParNew等收集器进行Full GC


    新生区Minor GC过程

    新生区内存较小,存储的对象生命周期非常短暂,因此高频率采用速度优先的复制算法进行GC
    1.      执行MinorGC时,
      if (存活对象分带年龄 >= MaxTenuringThreshold || To Survivor区容量达到阈值) {
        Eden区和From区中的存活对象===>>>养老区
    } else {
        Eden区存活对象===>>>To Survivor区
        From区中存活对象===>>>To Survivor区
    }
    2. 清理Eden和From区全部内存;
    3. 执行完Minor GC后,Eden区和From Survivor区清空,所有存活对象保存在To Survivor or 养老区区中;
    4. 交换From Survivor区和To Survivor区位置(保证有一块Survivor必须是空的)

    养老区Full GC过程

    养老区存储的对象生命周期非常长,并且养老区占据大部分堆区空间,因此养老区Full GC不采用复制算法


    垃圾收集器/Garbage Collector

    上图为JDK 1.7 Update14之后HotSpot中存在的收集器,两个收集器之间存在连线说明可以搭配使用;
    ★ STW(Stop-the-World)机制:运行GC时,GC收集器暂停所有的工作线程,直到GC完成后才恢复之前被暂停的工作线程;
    • 串行/并行回收方式:在GC过程中,单线程/多线程收集器采用Stop-the-World机制;
    • 并发回收:工作线程和垃圾回收线程并发执行;
    直至目前,最新的G1收集器也无法做到完全不需要Stop-the-World,因此收集器的设计目的在于程序高吞吐量GC低延迟


    常用垃圾收集器汇总


    Serial收集器Serial Old收集器ParNew收集器Parallel收集器Parallel Old收集器CMS收集器G1收集器
    回收方式单线程串行回收单线程串行回收并行回收并行回收并行回收并发回收并发回收
    工作场合新生区养老区新生区新生区养老区养老区新生区,养老区
    回收算法复制算法标记-整理算法复制算法复制算法标记-整理算法标记-清除算法复制算法
    是否STW机制
    适用场景单CPU宿主环境单CPU宿主环境多CPU宿主环境多CPU宿主环境多CPU宿主环境
    • Full GC次数频繁或消耗时间长
    • 对GC时间敏感的场合
    设计目标GC低延迟GC低延迟GC低延迟控制GC频率
    追求程序高吞吐量
    控制GC频率
    追求程序高吞吐量

    在实现高吞吐量的同时,尽可能满足GC低暂停时间
    补充说明缺省作为HopSpot Client模式下的新生区垃圾收集器
    • Serial收集器 的养老区版本
    • Serial Old收集器缺省作为HopSpot Client模式下的养老区垃圾收集器
    • 当CMS收集器发生Concurrent Mode Failure时使用
    • Serial收集器的多线程版本
    • 只有ParNew收集器可以与CMS收集器配合工作
    • 与ParNew的区别在于追求程序的高吞吐量
    • 能够动态调整进行JVM的调优任务
    • 无法与CMS收集器配合工作




    CMS收集器

    CMS(Concurrent Mark Sweep)收集器在JDK1.5中发布,工作在养老区,执行过程分为4步:
    1. 初始标记(Initial-Mark)/Stop-the-World
       标记所有和GC Roots连通的对象,标记期间采用Stop-the-World机制(短暂暂停);
    2. 并发标记(Concurrent-Mark)
       进行根追踪(GC-Roots-Tracing),将上一阶段中的不可达对象标记为垃圾对象;标记期间,标记线程和工作线程并发执行;
    3. 再次标记(Re-Mark)/Stop-the-World
       为防止并发标记阶段中被标记的垃圾对象的引用关系遭到修改,再次标记修正遭到修改的对象标记,标记期间采用Stop-the-World机制(短暂暂停);
    4. 并发清除(Concurrent-Sweep)
    • 经过前3个标记阶段,CMS并发清除掉所有垃圾对象的内存空间;
    • 清除期间,为保证用户线程正常工作,CMS会预留足够内存空间给用户线程,当预留内存无法满足需要,出现Concurrent Mode Failure失败,虚拟机临时启动Serial Old收集器进行GC;

    CMS缺陷

    • 产生大量内存碎片:由于CMS采用标记-清除算法,不可避免地会产生内存碎片,因此CMS分配内存空间时只能采用空闲链表法;
    • 无法实时处理垃圾:CMS并发清除阶段,无法实时处理用户工作线程产生的新垃圾,只能等待下一次GC时清理;
    • 对CPU资源非常敏感:并发阶段会占用CPU资源,尤其当发生Concurrent Mode Failure,调用Serial Old收集器后,延迟较大,导致程序吞吐量下降

    G1收集器(Garbage First)

    分代方式

    G1与其他收集器不同,没有选择在物理上隔离的新生区和养老区划分方式,而是在逻辑上划分为新生区和养老区;
    • G1将整个堆区划分为2048个大小相同的独立区域(Region);
    • Region块大小根据堆空间的实际大小决定(控制在1MB~32MB);
    • 新生区和养老区不再物理隔离,都是逻辑概念,都由一部分非连续的Region组成;
      • Eden Regions
      • Survivor Regions
      • Old generation Regions
      • Humongous Regions:专门用于存放大小超过Region 50%的巨型对象;

    Card TableRemember Set(RS)

    Card Table是一个全局Byte数组,
    • 将堆区按照512字节划分成块,每块对应一个Card;
    • 每个Card大小为1字节,标记为脏表示当前512字节长度的堆区块内存在对其他Region的引用;
    • Dirty Card Queue负责记录入队dirty card
    G1使用Remember Set记录Old Region是否引用Eden Region中的对象;
    • G1中每个Region都有一个对应的RS,负责记录其他Region对本Region的引用信息;
    • RS是一个Hash Table,每一个元素是一个Card地址,表示对应Card地址空间中存在对本Region的引用;
    • 判断对象存活时,GC-Roots通过扫描Eden Region的RS,而不必扫描Old Region;

    暂停预测模型(Pause Prediction Model)

    ★ 最大暂停时间:由参数XX:MaxGCPauseMillis指定;
    G1在后台用一个优先列表跟踪记录每个Region的回收收益,每次在最大暂停时间内,优先回收收益最大的Region,即Garbage First;
    G1使用暂停预测模型在目标暂停时间范围内,来选择回收的Region数量;

    GC模式一:Young GC

    Young GC是对Eden Region进行GC,在Eden Region空间耗尽时触发,执行期间采用STW机制;
    0. Stop-the-World;
    1. 根区域扫描:
    2. 更新Remember Set:清空Dirty Card Queue更新Remember Set;
    3. 处理Remember Set:通过RS找打Eden Region内被Old Region引用的对象;
    4. 复制算法:优先回收收益最大的Eden Region,拷贝存活对象到Survivor/Old Region,清理Eden Region空间;
    5. 处理引用队列(软引用、弱引用、虚引用处理);

    GC模式二:Mixed GC过程

    Mixed GC对新生区和养老区进行GC,当堆区使用率达到阈值(默认45%)时触发;
    G1的执行过程分为6个阶段:
    1. 初始标记(Initial-Mark)
       执行一次Young GC;标记所有和GC Roots连通的Region块,标记期间采用STW机制(短暂暂停);
    2. 根区域扫描(Root-Region-Scanning)
        
       可与用户程序并发执行,不可被Young GC中断;
    3. 并发标记(Concurrent-Marking)
       采用STW机制,在整个堆中进行并发标记, 标记出所有存活对象和垃圾对象;
       虚拟机用线程Remember Set Logs记录并发标记阶段标记状态发生的对象记录;
      • Remember Set Logs本质是每个线程独有的一个大小为256的缓冲区,存放dirty card地址
       可被Young G和STW中断,可以和用户程序并发执行,耗时较长;
    4. 再次标记(Remark)/Stop-the-World
       G1采用SATB(Snapshot-at-the-begining)缓冲区再次标记对象并发标记阶段标记状态发生改变的对象)
          
    再次标记阶段负责将Remember Set Logs中数据合并到Remember Set中;
    5. 清除(Cleanup)和拷贝(Copying)
    • 清除目标:优先回收收益最大的Eden Regions和Old Regions;
    • 清除和拷贝:清除时采用复制算法,把存活对象复制到空闲Region,然后清空原Region,将清空后的Region加入空闲Region链表中;
    • 期间可与用户程序并发执行;

    G1收集器与CMS收集器对比

    • G1通过划分Region方式避免内存碎片问题CMS区新生区和养老区位置固定,G1中仅是逻辑概念,在内存使用上更灵活;
    • G1通过设置预期停顿时间(Pause Time),能让用户明确指定一个最大垃圾收集时间,使得GC时间不得超过该目标实际,来控制垃圾收集时间避免应用雪崩现象;
    • G1在回收内存后马上整理空闲内存,CMS默认在STW的时候做;
    • G1在新生区和养老区工作,CMS只能在养老区工作;



     

  • 相关阅读:
    Nacos启动异常:failed to req API:/api//nacos/v1/ns/instance after all servers([127.0.0.1:8848])
    多节点集群思路
    内网dns配置
    MySQL集群配置思路
    pycharm常用快捷键
    2020年11月新版CKA考试心得
    JavaScript的Map、Set、WeakMap和WeakSet
    AJAX传输二进制数据
    linux性能监测与优化的指令
    八千字硬核长文梳理Linux内核概念及学习路线
  • 原文地址:https://www.cnblogs.com/yzwall/p/6637160.html
Copyright © 2020-2023  润新知