触发垃圾回收的方式:
- 内存不足时:堆内存不足,无法为新对象分配内存时触发垃圾回收
- 手动请求建议:调用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队列中,在最终标记阶段,会将队列中存在于快照的对象标记为黑色,避免漏标
评论前必须登录!
注册