云计算百科
云计算领域专业知识百科平台

JVM——垃圾回收

触发垃圾回收的方式:

  • 内存不足时:堆内存不足,无法为新对象分配内存时触发垃圾回收
  • 手动请求建议:调用System.gc()或Runtime.getRuntime.gc()建议垃圾回收,不保证立即执行
  • 设置JVM参数:-Xmx(最大堆内存),-Xms(初始堆内存),-Xmn(新生代内存)
  • 自定义阈值:不同垃圾回收器内部实现了不同策略,设置对应的对象数量或内存空间阈值

判断对象是否为垃圾的方法有哪些?

引用计数法:

  • 原理:为每个对象分配一个引用计数器,每当有一个地方引用该对象,计数器加1,每当引用变量被移除或指向其他对象时,计数器减1。计数器为0时表示没有引用,可以被回收。
  • 缺陷:不能解决循环引用问题,即两个对象互相引用或引用链成环,即使这些对象不被使用也无法被回收

可达性分析算法:

原理:从一组GC root(垃圾收集根)对象出发,向下追溯引用链(类似于深度搜索),若某个对象不与任何引用链相连,那么就认为这个对象是不可达的,可以被回收。

GC root包含:
  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈中引用的对象
  • 方法区中静态属性对象和常量对象
  • 持有锁的对象

垃圾回收算法有哪些?

标记—清除算法:

  • 原理:利用可达性分析标记出需要回收的所有对象,再统一回收
  • 缺陷:标记和清除的过程效率都不高,且清除结束后容易产生大量碎片化空间,不利于空间的再利用。

复制算法:

  • 原理:将内存分为两部分(from和to区域),每次申请内存时使用其中一块(作为from区域)。在可达性分析的过程中将存活对象按顺序紧凑复制到to区域(空闲内存块)中。最后清理from和Eden区域。
  • 缺陷:每次只能使用一半的内存,内存利用率低。无需显式清除垃圾对象,存活对象较多时由于复制较多导致效率较低

标记—整理(压缩)算法:

  • 原理:标记过程与标记—清除算法一致,但标记后不会立即清理,而是将所有存活对象都移动到内存的一端。移动结束后直接清理剩余部分。

分代回收算法:

  • 将内存划分为新生代和老年代。分配依据是对象的生命周期。对象创建时会在新生代中申请内存,新生代的每次GC都会使存活下来的对象年龄+1,当年龄超过阈值(默认15)时会将对象晋升到老年代中。(若幸存区内存超过百分之50也会直接将年龄较大的对象直接晋升到老年代中,避免频繁GC)

STW(stop the world)

STW会暂停所有用户线程(如业务逻辑、IO操作),以确保GC准确识别存活对象与垃圾。

否则可能导致以下问题:

  • 引用关系变化​
    • 例如:线程 A 正在扫描对象 X,而线程 B 突然修改 X 的引用,导致 GC 误判(漏标或错标)。
  • ​对象移动问题​
    • 如果 GC 正在压缩堆(如 Serial / Parallel GC 的整理阶段),用户线程可能访问到已被移动的旧地址,导致内存访问错误。
  • ​并发竞争​
    • 用户线程和 GC 线程同时操作堆内存,可能引发数据竞争(Data Race),破坏堆的完整性。
  • 垃圾回收器有哪些?

    响应速度优先是什么:

    单次STW时间更短但更频繁或允许用户线程和GC线程并发,对客户端交互的响应速度更快。

    吞吐量优先是什么:

    单次STW时间更长,但次数少,适合计算量大且交互不多的场景。

    CMS回收器详解:

    老年代的一种并发低延迟垃圾回收器,旨在减少STW时间,适用于响应时间敏感的服务

    核心特点:

    • 并发标记清理:
    • 标记—清除算法:
    • 分代回收:通常与parNew配合使用

    工作流程:

    1、初始标记(STW):
    • 快速标记GC Roots对象,STW时间极短
    2、并发标记:
    • 递归遍历引用链,标记所有存活对象
    • 并发执行,不阻塞用户线程
    • 由于用户线程可能修改引用,导致“浮动垃圾”(本次GC无法回收)
    3、重新标记(STW):
    • 修正并发时用户线程更改的引用(关注新增引用,防止回收新的存活对象,但无法避免浮动垃圾)
    • 比初始标记时间稍长,但比full GC短
    4、并发清除:
    • 清除未标记对象,回收内存,但会产生碎片化内存
    • 并发执行,不阻塞用户线程
    Full GC(失败退化)
    • 长期碎片化内存堆积可能导致晋升失败,退化为Serial Old GC(单线程标记—整理),大幅增加STW时间

    G1回收器详解:

    独特动态内存模型:

    不再物理严格划分代,而是逻辑保留代的概念,通过Region(区域)动态填充

    G1将堆划分为多个固定大小的Region(1mb~32mb,可通过-XX:GHeapRegionSize调整),并动态分配给不同代:

    • Eden Regions(伊甸区)
    • Survivor Regions(幸存区)
    • Old Regions(老年代)
    • Humongous Regions(存放超过Region大小50%的对象)

    优势:

    • 避免全堆回收,只回收包含垃圾最多的Region
    • 大对象单独存储,减少复制开销

    工作流程:

    回收过程分为Minor GC和Mixed GC,并最终可能触发full GC(应该尽量避免)

    Minor GC(STW)

    类似于ParNew回收器,但是使用Region

    • Eden区满时触发
    • 并行标记存活对象(STW)
    • 复制存活对象(STW)到Survivor区或Old区
    • 清空Eden区和from区,调整Region布局
    并发标记周期(类似于CMS的回收流程)
    • 老年代达到阈值(-XX:InitiatingHeapOccupancyPercent,默认45%)时执行
    1、初始标记(STW):
    • 快速标记GC Roots对象,STW时间极短
    2、并发标记:
    • 递归遍历引用链,标记所有存活对象
    • 并发执行,不阻塞用户线程
    • 由于用户线程可能修改引用,导致“浮动垃圾”(本次GC无法回收)
    3、最终标记(STW):
    • 修正并发时用户线程更改的引用(关注新增引用,防止回收新的存活对象,但无法避免浮动垃圾)
    4、统计存活对象
    • 统计Region的存活对象,为Mixed GC做准备
    Mixed GC(STW)
    • 并发标记完成后,选择垃圾比例高的Region进行回收
    • 复制存活对象到空闲Region(类似Minor GC)
    • 清理垃圾Region,调整内存布局
    Full GC(失败回退)
    • 并发模式失败(回收速度跟不上分配速度)或堆内存不足时触发
    • 退化为Serial Old(单线程标记—整理),STW时间长

    G1与CMS的对比:

    使用范围不同:
    • CMS是老年代收集器
    • G1收集范围是新生代和老年代
    STW时间:
    • CMS以最小的停顿时间为目标
    • G1可预测垃圾回收的停顿时间,兼顾响应速度和吞吐量
    垃圾碎片:
    • CMS会产生碎片化内存
    • G1使用的是复制算法,无碎片化内存

    跨代引用及优化方式

    跨代引用指新生代和老年代对象间可能存在互相引用的关系,在执行minor gc时就需要进行全堆扫描来确定幸存对象

    解决方案:

    1、记忆集:

    记录老年代区域指向新生代区域的引用,直接加入到GC roots中

    2、卡表:
    • 记忆集的具体实现,将老年代划分为多个卡页(默认512字节)
    • 写屏障:引用变动时动态更新卡表,标记脏卡
    • minor GC扫描时只需要扫描脏卡页而无需全堆扫描
    3、RSet:
    • G1回收器对记忆集的具体实现,类似于卡表
    • Region内部会记录其他Region对当前Region对象的引用

    并发标记时的并发安全:

    CMS增量更新:

    • 当出现黑色对象新增与白色对象的引用时,写屏障会拦截这一操作,并将该黑色对象加入增量更新队列中
    • 重新标记阶段,CMS会遍历增量更新队列,将黑色对象降级为灰色重新扫描引用链,避免漏标

    G1快照(satb):

    • 记录并发标记开始前的引用关系作为快照,写屏障会拦截引用更新(覆盖或删除)并记录旧引用
    • 若出现快照中不存在的新对象,则直接标记为存活状态(隐式标记,不染色)
    • 对于引用删除,写屏障会记录旧引用,并加入satb队列中,在最终标记阶段,会将队列中存在于快照的对象标记为黑色,避免漏标  
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » JVM——垃圾回收
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!