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准确识别存活对象与垃圾

否则可能导致以下问题:

  1. 引用关系变化
    • 例如:线程 A 正在扫描对象 X,而线程 B 突然修改 X 的引用,导致 GC 误判(漏标或错标)
  2. 对象移动问题
    • 如果 GC 正在压缩堆(如 Serial / Parallel GC 的整理阶段) ,用户线程可能访问到已被移动的旧地址,导致内存访问错误
  3. 并发竞争
    • 用户线程和 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队列中 ,在最终标记阶段 ,会将队列中存在于快照的对象标记为黑色,避免漏标
相关推荐
江湖中的阿龙3 小时前
JVM监控工具
jvm
CrazyClaz13 小时前
JVM(Java虚拟机)
java·jvm
Java永无止境15 小时前
JavaSE常用API之Runtime类:掌控JVM运行时环境
java·开发语言·jvm
爱喝阔落的猫15 小时前
【JVM 02-JVM内存结构之-程序计数器】
jvm
爱喝阔落的猫20 小时前
【JVM 05-JVM内存结构之-堆】
jvm
加什么瓦1 天前
JVM——内存模型
java·开发语言·jvm
观音山保我别报错1 天前
JVM 的垃圾回收机制 GC
java·开发语言·jvm
爱喝阔落的猫2 天前
【JVM 01-引言入门篇】
jvm
观音山保我别报错2 天前
JVM 双亲委派模型
jvm