当前位置: 首页 > news >正文

JVM垃圾回收的时机是什么时候(深入理解 JVM 垃圾回收时机:什么时候会触发 GC?)

深入理解 JVM 垃圾回收时机:什么时候会触发 GC?

在 Java 开发中,我们常听说 “JVM 会自动进行垃圾回收”,但很少有人能说清:GC 究竟在什么情况下会被触发?是到固定时间就执行?还是内存满了才会启动?其实,JVM 的垃圾回收时机并非 “一刀切”,而是由内存状态、GC 算法策略和用户配置共同决定的动态行为。今天我们就从实际场景出发,拆解 GC 的触发机制。

一、最常见的触发场景:内存不足了

当 JVM 无法为新对象分配内存时,会 “被动” 触发垃圾回收 —— 这是 GC 最核心、最频繁的触发原因,也是我们日常开发中最需要关注的场景。根据内存区域的不同,又可分为三类:

1. 新生代内存不足:触发 Minor GC(Young 分为三类:

1. 新生代内存不足:触发 Minor GC(Young GC)

新生代是 Java 对象的 “出生地”,绝大多数新创建的对象(如new User()、new int[10])都会先分配到新生代的 Eden 区。由于新生代空间通常较小(比如几十到几百 MB,通过-Xmn配置),Eden 区很容易被填满。

触发逻辑

当 Eden 区满了,JVM 无法为新对象分配内存时,会立即触发Minor GC—— 只针对新生代(Eden 区 + 两个 Survivor 区)进行回收,清理掉 “无用对象”(没有任何引用指向的对象)。

举个例子

假设 Eden 区大小为 100MB,我们循环创建 1000 个 100KB 的对象,当创建到第 1001 个时,Eden 区已被占满,JVM 会触发 Minor GC,回收掉其中已无引用的对象(比如前 500 个已被赋值为null的对象),释放空间后继续分配新对象。

特点

  • 频率高(可能每秒触发多次);
  • 耗时短(新生代对象存活时间短,大部分对象会被回收,且 GC 过程中只有部分阶段会暂停用户线程);
  • 不会影响老年代(Minor GC 只处理新生代)。

2. 老年代内存不足:触发 Major GC/Full GC

老年代存储的是 “存活时间长” 的对象 —— 比如频繁被引用的单例对象、从新生代多次 Minor GC 后存活下来的对象(默认存活 15 次 Minor GC 后会晋升到老年代)。当老年代空间不足时,会触发更 “重量级” 的回收。

触发逻辑

有两种典型情况会导致老年代内存不足:

  1. 新生代对象晋升到老年代时,发现老年代空间不够(比如一个大对象从 Eden 区直接晋升,而老年代剩余空间不足);
  1. 老年代自身的对象积累过多,可用空间低于阈值。

此时 JVM 会触发Major GC—— 主要回收老年代的无用对象,部分情况下会同时回收新生代(这种跨区域的回收称为 Full GC)。

注意

如果 Major GC 后,老年代仍无法释放足够空间,JVM 会抛出OutOfMemoryError: Java heap space—— 这是我们常遇到的 “内存溢出” 错误,需要通过调整堆大小(-Xmx)或优化对象生命周期来解决。

特点

  • 频率低(可能几分钟甚至几小时触发一次);
  • 耗时长(老年代对象存活时间长,需要更复杂的扫描和判断,且 Full GC 会导致 “Stop The World”(STW)—— 暂停所有用户线程,可能造成业务卡顿)。

3. 方法区(元空间)内存不足:触发元空间 GC

JDK 8 之后,方法区的实现从 “永久代” 改为 “元空间”,主要存储类的元信息(如类名、字段、方法代码)、常量池和静态变量。元空间默认使用 “本地内存”(不受 JVM 堆大小限制),但并非无限 —— 当元空间内存不足时,也会触发 GC。

触发逻辑

当动态生成大量类(比如使用 CGLIB 代理、反射生成类),导致元空间存储的类信息过多,超过了系统可用的本地内存时,JVM 会触发元空间的 GC,清理掉 “不再使用的类”(比如类加载器已被回收、类的所有实例已被回收)。

如果回收后仍不足

JVM 会抛出OutOfMemoryError: Metaspace—— 这种错误常见于频繁动态生成类的场景(如某些 ORM 框架、动态代理框架使用不当)。

二、主动触发:GC 算法的 “策略性回收”

除了 “内存不足” 这种被动情况,JVM 也会根据 GC 算法的预设策略,在内存暂时充足时 “主动” 触发 GC,目的是避免内存过度占用后集中回收导致的性能波动。

1. 定时扫描:并发 GC 的后台工作

对于支持 “并发回收” 的 GC 算法(如 CMS、G1),JVM 会启动专门的 “后台回收线程”,定期扫描内存区域(比如每几秒一次),主动寻找无用对象并回收。这种方式可以 “见缝插针” 地释放内存,减少 Full GC 的频率。

比如 CMS 算法的 “并发标记” 阶段,后台线程会在用户线程运行的同时,悄悄扫描老年代的对象引用,标记出无用对象,后续再通过短时间的 STW 阶段完成回收 —— 整个过程对业务的影响很小。

2. 大对象分配前的 “预判回收”

JVM 对 “大对象”(比如超过 Eden 区一半大小的数组、大字符串)有特殊处理逻辑:为了避免大对象直接进入老年代(可能快速耗尽老年代空间),在分配大对象前,JVM 会先触发一次 Minor GC,尝试释放 Eden 区的空间。如果释放后仍无法容纳大对象,才会将其直接分配到老年代。

举个例子

Eden 区大小为 100MB,我们要创建一个 60MB 的数组(属于大对象)。此时 JVM 会先触发 Minor GC,回收 Eden 区中无用的对象(假设释放了 40MB 空间),但 Eden 区剩余空间(40MB)仍不足以容纳 60MB 的数组,最终会将数组直接分配到老年代。

3. 内存使用率达到阈值:提前预防

部分 GC 算法支持通过参数配置 “内存使用率阈值”,当内存使用率达到阈值时,主动触发 GC,避免内存被完全占满。

最典型的是 CMS 算法的-XX:CMSInitiatingOccupancyFraction参数:默认值为 92,表示当老年代使用率达到 92% 时,会主动触发 CMS 回收。如果不提前触发,等到老年代满了再回收,可能会被迫执行 “Serial Old GC”(一种更慢的 Full GC),导致更长时间的 STW。

三、不推荐的方式:手动触发 GC

Java 提供了手动 “建议” JVM 执行 GC 的 API,但请注意:这只是建议,不是强制 ——JVM 可以忽略你的请求。

// 两种手动触发GC的方式(效果完全相同)System.gc();Runtime.getRuntime().gc();

为什么不推荐?

  1. 破坏 JVM 的自动优化:JVM 会根据内存状态动态调整 GC 时机,手动触发可能打乱其优化策略(比如明明内存还充足,却强制触发 Full GC,导致性能浪费);
  1. 可能导致业务卡顿:手动调用System.gc()大概率会触发 Full GC,造成 STW,影响用户体验;
  1. 无法解决根本问题:如果频繁需要手动触发 GC,说明代码存在内存泄漏或对象生命周期设计不合理,应该优化代码而非依赖手动 GC。

例外场景

仅在测试环境(如验证内存泄漏是否修复)或工具类(如 JVM 监控工具)中,才可能偶尔使用手动 GC,生产环境绝对禁止。

四、特殊场景:JVM 退出或动态扩容时

除了上述常规场景,还有两种特殊情况会触发 GC:

1. JVM 进程退出前

当 JVM 进程即将退出时(比如执行System.exit(0)、程序正常结束),会触发一次 Full GC—— 但此时回收内存已无实际意义,更多是 JVM 的 “清理流程”,确保资源正常释放。

2. 堆内存动态扩容时

JVM 堆内存支持动态扩容(默认开启,通过-Xms设置初始大小,-Xmx设置最大大小)。当堆内存从初始大小向最大大小扩容时,如果扩容后的空间仍不足以分配新对象,会触发 GC,尝试释放内存后再继续扩容。

总结:GC 时机的核心原则

JVM 垃圾回收的触发时机,本质是 “按需触发,策略辅助”:

  1. 核心驱动力:内存不足(新生代满、老年代满、元空间满)—— 这是 GC 最根本的触发原因;
  1. 优化策略:定时扫描、大对象预判、阈值触发 —— 这些是为了减少 Full GC 频率,提升性能;
  1. 禁止手动干预:手动触发 GC 会破坏 JVM 的自动优化,生产环境绝对避免。

理解 GC 的触发机制,能帮助我们更好地排查内存问题:比如遇到频繁 Minor GC,可能是新生代空间太小;遇到频繁 Full GC,可能是老年代有内存泄漏或大对象过多。后续我们还会深入讲解不同 GC 算法的具体实现,敬请关注!


文章转载自:

http://VnkzIpl2.prqdr.cn
http://nusV9seQ.prqdr.cn
http://b8wVtF7Y.prqdr.cn
http://FRXcjDmn.prqdr.cn
http://X6iJcEMh.prqdr.cn
http://iIRR53HX.prqdr.cn
http://w0PsNaAB.prqdr.cn
http://sKa66Boe.prqdr.cn
http://AGIbl2sH.prqdr.cn
http://y5ryhWiu.prqdr.cn
http://Q0OWj3pz.prqdr.cn
http://PKe5GuRn.prqdr.cn
http://G6yXnjD4.prqdr.cn
http://f6ycMPKg.prqdr.cn
http://xZj2t2Sp.prqdr.cn
http://amVC0DN1.prqdr.cn
http://BjaITE3m.prqdr.cn
http://eKvBex6u.prqdr.cn
http://SONzm7UW.prqdr.cn
http://LRijGQ6O.prqdr.cn
http://MGUVsS3h.prqdr.cn
http://aQPxrWJC.prqdr.cn
http://K9flQ5H3.prqdr.cn
http://nUMqNuq0.prqdr.cn
http://AClZsbVe.prqdr.cn
http://JwTrgrBZ.prqdr.cn
http://ekq4jaqM.prqdr.cn
http://M8kTmymT.prqdr.cn
http://j5MDwKgt.prqdr.cn
http://bJw2wdzp.prqdr.cn
http://www.dtcms.com/a/379016.html

相关文章:

  • Python 版本和Quantstats不兼容的问题
  • SFINAE
  • TCP 三次握手与四次挥手
  • 【iOS】UIViewController生命周期
  • 硬件开发(7)—IMX6ULL裸机—led进阶、SDK使用(蜂鸣器拓展)、BSP工程目录
  • 人工智能学习:Transformer结构中的编码器层(Encoder Layer)
  • RISCV中PLIC和AIA的KVM中断处理
  • 掌握梯度提升:构建强大的机器学习模型介绍
  • 全球智能电网AI加速卡市场规模到2031年将达20216百万美元
  • springbook3整合Swagger
  • LMS 算法:抗量子时代的「安全签名工具」
  • CUDA中thrust::device_vector使用详解
  • Python学习-day8 元组tuple
  • 2025主流大模型核心信息
  • skywalking定位慢接口调用链路的使用笔记
  • LeetCode刷题记录----739.每日温度(Medium)
  • eNSP华为无线网测试卷:AC+AP,旁挂+直连
  • 开源多模态OpenFlamingo横空出世,基于Flamingo架构实现图像文本自由对话,重塑人机交互未来
  • 光路科技将携工控四大产品亮相工博会,展示工业自动化新成果
  • matlab实现相控超声波成像仿真
  • 【C】Linux 内核“第一宏”:container_of
  • Dinky 是一个开箱即用的一站式实时计算平台
  • Vue3内置组件Teleport/Suspense
  • Python打印格式化完全指南:掌握分隔符与行结尾符的高级应用
  • 实体不相互完全裁剪,请检查您的输入
  • 分数阶傅里叶变换(FRFT)的MATLAB实现
  • ARM (6) - I.MX6ULL 汇编点灯迁移至 C 语言 + SDK 移植与 BSP 工程搭建
  • unsloth微调gemma3图文代码简析
  • 【ECharts ✨】ECharts 自适应图表布局:适配不同屏幕尺寸,提升用户体验!
  • wpf依赖注入驱动的 MVVM实现(含免费源代码demo)