深入理解 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://www.pswp.cn/diannao/99137.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/99137.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/99137.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在Vue项目中Axios发起请求时的小知识

在Vue项目中Axios发起请求时的小知识 在Vue项目开发中,Axios作为基于Promise的HTTP客户端,凭借其简洁的API设计和强大的功能(如请求/响应拦截、自动JSON转换、取消请求等),已成为前端与后端通信的主流选择。本文将深入…

GeoHash分级索引技术

GeoHash分级索引技术是一种将二维地理坐标转换为一维字符串的空间索引方法,其核心是通过分级网格划分和前缀编码实现高效的空间数据检索。以下从技术原理、实现细节到工程优化展开详细解析: 一、编码原理与分级结构 1. 经纬度二进制化 GeoHash通过递归二分地球表面生成网格…

HTML HTML基础(4)

1.列表 (1).有序列表 概念&#xff1a;有顺序或侧重顺序的列表。 <h2>要把大象放冰箱总共分几步</h2> <ol> <li>把冰箱门打开</li> <li>把大象放进去</li> <li>把冰箱门关上</li> </ol> (2).无序列表 概念&a…

MySQL中的回表操作

在数据库查询&#xff08;尤其是基于 B树索引 的关系型数据库&#xff0c;如MySQL、PostgreSQL&#xff09;中&#xff0c;“回表”是一个核心且高频出现的概念&#xff0c;直接影响查询性能。要理解回表&#xff0c;需先理清索引结构与数据存储的关联&#xff0c;再拆解其发生…

QT子线程与GUI线程安全交互

在Qt应用程序开发中&#xff0c;涉及到多线程处理时&#xff0c;如何安全地从子线程更新UI界面是一个常见的问题。Qt的UI界面并不是线程安全的&#xff0c;意味着你不能直接在子线程中操作UI组件&#xff08;比如按钮、标签等&#xff09;。如果不遵循线程安全的规则&#xff0…

RL【10-2】:Actor - Critic

系列文章目录 Fundamental Tools RL【1】&#xff1a;Basic Concepts RL【2】&#xff1a;Bellman Equation RL【3】&#xff1a;Bellman Optimality Equation Algorithm RL【4】&#xff1a;Value Iteration and Policy Iteration RL【5】&#xff1a;Monte Carlo Learnin…

开源大模型天花板?DeepSeek-V3 6710亿参数MoE架构深度拆解

文章目录认知解构&#xff1a;DeepSeek的定位与核心价值模型概述与发展历程创立初期与技术奠基&#xff08;2023年7月-2024年11月&#xff09;里程碑一&#xff1a;MoE架构规模化突破&#xff08;2024年12月&#xff09;里程碑二&#xff1a;推理成本革命性优化&#xff08;202…

10 训练中的一些问题

&#x1f31f; 大背景&#xff1a;训练神经网络 下山寻宝 训练神经网络就像你蒙着眼在一座大山里&#xff0c;想找最低点&#xff08;最小损失&#xff09;。你只能靠脚下的坡度&#xff08;梯度&#xff09;来决定往哪儿走。 你的位置 模型参数&#xff08;权重 www&#xf…

synchronized锁升级的过程(从无锁到偏向锁,再到轻量级锁,最后到重量级锁的一个过程)

锁升级是 Java 中 synchronized 锁 的核心优化机制&#xff08;基于 JVM 的 对象头 Mark Word 实现&#xff09;&#xff0c;指锁的状态从 无锁 → 偏向锁 → 轻量级锁 → 重量级锁 逐步升级的过程。其目的是通过 “按需升级”&#xff0c;在不同并发场景下选择最优的锁实现&am…

HOT100--Day25--84. 柱状图中最大的矩形,215. 数组中的第K个最大元素,347. 前 K 个高频元素

HOT100–Day25–84. 柱状图中最大的矩形&#xff0c;215. 数组中的第K个最大元素&#xff0c;347. 前 K 个高频元素 每日刷题系列。今天的题目是《力扣HOT100》题单。 题目类型&#xff1a;栈&#xff0c;堆。 84. 柱状图中最大的矩形 思路&#xff1a; class Solution {publ…

基于 Apache Doris 的用户画像数据模型设计方案

一、 需求分析与设计目标数据源&#xff1a;用户基本信息&#xff1a;用户ID、性别、出生日期、注册时间、常驻地域&#xff08;省、市、区&#xff09;、职业等。用户体检报告&#xff1a;每次体检的报告ID、体检时间、各项指标&#xff08;如血压、血糖、血脂、BMI等&#xf…

Python的深度学习

深入理解Python高级特性掌握Python的高级特性是进阶的关键&#xff0c;包括装饰器、生成器、上下文管理器、元类等。这些特性能够提升代码的灵活性和效率。例如&#xff0c;装饰器可以用于实现AOP&#xff08;面向切面编程&#xff09;&#xff0c;生成器可以处理大数据流而无需…

数据库范式(Normalization)

一个设计混乱的数据库就像一个杂乱的房间&#xff0c;用起来非常不方便&#xff1a;东西到处乱放&#xff08;数据冗余&#xff09;&#xff0c;找件东西要翻遍所有角落&#xff08;查询困难&#xff09;&#xff0c;扔掉一把旧椅子时&#xff0c;可能会把搭在上面的唯一一件外…

数据结构---循环队列

基于循环数组实现的循环队列解决了顺序队列中的假溢出导致的空间浪费问题操作&#xff1a;&#xff08;1&#xff09;初始化//循环队列 typedef struct {int *data;//指针模拟声明数组int head,tail;//队头&#xff0c;队尾 }Queue; //初始化 Queue *InitQueue() {Queue *q (Q…

深入理解线程模型

线程作为操作系统调度的基本执行单元&#xff0c;是实现高吞吐、低延迟系统的基础。一、进程与线程的体系结构对比核心概念&#xff1a;进程&#xff08;Process&#xff09;&#xff1a;操作系统资源分配的基本单位&#xff0c;拥有独立的虚拟地址空间、文件描述符表、环境变量…

TTC定时器中断——MPSOC实战3

开启TTC定时器&#xff0c;不同于7000系列的私有定时器此处设置LPD_LSBUS频率TTC频率取决于LPD_LSBUS可前往指定位置查看参数不使能填写对应宏可前往指定位置查看参数main.c#include <stdio.h> #include "xparameters.h" #include "xgpiops.h" #incl…

人工智能训练师三级备考笔记

一、实操1&#xff09;通用语法&#xff08;常见于实操题第一块代码块&#xff09;1.读取文件数据或加载数据集等描述时一般为以下结构&#xff1a;Datapd.read_文件格式(文件名) 注意&#xff1a;文件名需要用‘ ’框起来&#xff0c;必须要有引号文件格式有以下内容csv、txt…

Cherry Studio递归工具调用机制深度解析

在现代AI应用开发中,工具调用(Tool Calling)已成为大语言模型与外部系统交互的核心机制。Cherry Studio作为一款先进的AI对话客户端,实现了一套完整的递归工具调用系统,能够让AI助手在执行复杂任务时自动调用多个工具,并根据执行结果智能决策下一步操作。本文将深入解析这…

[哈希表]966. 元音拼写检查器

966. 元音拼写检查器 class Solution:def spellchecker(self, wordlist: List[str], queries: List[str]) -> List[str]:origin set(wordlist) # 存储原始单词用于完全匹配lower_to_origin {} # 存储小写形式到原始单词的映射vowel_to_origin {} # 存储元音模糊形…

正则表达式与文本三剑客(grep、sed、awk)基础与实践

正则表达式基础与实践一、正则表达式概述1. 定义正则表达式&#xff08;Regular Expression&#xff0c;简称 RE&#xff09;是用于描述字符排列和匹配模式的语法规则&#xff0c;核心作用是对字符串进行分割、匹配、查找、替换操作。它本质是 “模式模板”&#xff0c;Linux 工…