JVM垃圾回收(GC)全维度解析:从原理到调优实战

JSON 2026-02-06 16:40:48 420

一、GC的核心目标与回收范围

1.1 核心使命

GC的核心是解决两大核心问题,平衡性能与稳定性:

  • 识别垃圾:精准定位堆内存中“不再被程序访问的对象”,避免无效对象占用内存;
  • 高效回收:在尽可能少影响应用运行的前提下,回收垃圾、整理内存碎片,避免OOM(内存溢出),同时平衡吞吐量(应用运行时间占比)和响应时间(GC停顿对业务的影响)。

1.2 核心回收范围(关键前提)

GC的核心回收范围仅为JVM堆内存(新生代+老年代),其他内存区域不参与核心GC流程:

  • 元空间(JDK8引入,替代永久代):仅在内存满时触发少量回收,不属于核心GC范畴;
  • 线程私有内存(虚拟机栈、本地方法栈、程序计数器):随线程销毁自动释放,无需GC干预。

二、垃圾对象的判断:GC的底层逻辑

回收垃圾的前提是“定义垃圾”——即当前程序无法访问到的对象。JVM共存在两种判断算法,其中可达性分析算法是现代JVM的唯一实现。

2.1 引用计数法(已淘汰)

核心原理

给每个对象分配一个引用计数器,通过计数器数值判断对象是否为垃圾:

  • 对象被引用时,计数器+1;
  • 引用失效时,计数器-1;
  • 当计数器值为0时,判定为垃圾对象,可回收。

致命缺陷

无法解决循环引用问题:两个对象互相引用时,计数器均为1,即便无任何外部引用,也永远无法被回收,最终导致内存泄漏。

// 循环引用示例(引用计数法无法识别)
Object a = new Object();
Object b = new Object();
a.ref = b; // a引用b,b计数器+1
b.ref = a; // b引用a,a计数器+1
a = null;  // a引用失效,a计数器-1(变为1)
b = null;  // b引用失效,b计数器-1(变为1)
// 此时a、b无外部引用,但计数器均为1,无法被回收

2.2 可达性分析算法(JVM核心实现)

核心原理

GC Roots(垃圾回收根节点)为起点,向下遍历所有对象的引用链,通过引用链是否断裂判断对象存活状态:

  • 能通过GC Roots到达的对象(存在完整引用链)→ 存活对象,不可回收;
  • 无法通过GC Roots到达的对象(引用链断裂)→ 垃圾对象,可回收。

关键:GC Roots的核心类型

GC Roots是JVM中“绝对不会被回收”的堆外对象,核心包含5类,无需记忆所有细节,重点掌握前3类:

  1. 虚拟机栈(栈帧局部变量表)中引用的对象(如方法内定义的局部变量);
  2. 方法区中类静态属性引用的对象(如public static Object obj = new Object());
  3. 方法区中常量引用的对象(如public static final Object obj = new Object());
  4. 本地方法栈(Native方法)中JNI(Java Native Interface)引用的对象;
  5. JVM内部核心对象(如Class对象、线程对象、类加载器ClassLoader)。

2.3 补充:Java的4种引用类型(影响GC回收策略)

JDK1.2后,Java将引用分为4种强度(从强到弱),不同引用类型的对象,GC回收策略不同,是缓存设计的核心依据,需重点区分:

引用类型
核心特点
垃圾回收策略
典型应用场景
强引用
普通引用(如Object obj = new Object()),日常开发最常用
只要存在强引用,无论内存是否充足,永远不回收(即使OOM)
绝大多数业务场景(如实体类对象、工具类对象)
软引用
SoftReference类包装,引用强度弱于强引用
内存充足时不回收,内存不足时(OOM前最后防线)主动回收
图片缓存、数据缓存(允许缓存失效,避免OOM)
弱引用
WeakReference类包装,引用强度弱于软引用
无论内存是否充足,只要触发GC,立即回收
ThreadLocal的key、临时缓存(无需长期保留,随GC自动清理)
虚引用
PhantomReference类包装,需配合引用队列使用,引用强度最弱
无法通过虚引用获取对象,仅用于跟踪对象的回收时机
堆外内存回收(如DirectByteBuffer,跟踪回收时机释放堆外内存)

三、分代回收模型:GC高效的核心思想

现代JVM GC的核心优化思路是“分代收集”——根据对象的存活周期,将堆内存划分为不同区域,对不同区域使用适配的GC算法/收集器,从而提升回收效率。其核心依据是:98%的对象“朝生夕死”(创建后很快被回收),仅少数对象能长期存活。

3.1 堆内存分代划分(JDK8主流)

JDK8移除了永久代,替换为元空间(占用本地内存),堆内存的核心结构如下(重点记忆比例和区域功能):

JVM堆内存(总大小由-Xms/-Xmx控制) ├─ 新生代(占堆总大小的1/3左右):存放新创建的对象,回收频率高 │ ├─ Eden区(占新生代的8/10):新对象优先分配的区域,绝大多数对象在此创建 │ ├─ Survivor 0区(S0,占新生代的1/10):存活对象临时存放区 │ └─ Survivor 1区(S1,占新生代的1/10):存活对象临时存放区(与S0互斥,永远一个为空) └─ 老年代(占堆总大小的2/3左右):存放长期存活的对象,回收频率低

新生代关键规则(必记)

  • Eden区内存满时,触发Minor GC(新生代GC),仅回收新生代垃圾;
  • S0和S1永远有一个为空,用于标记-复制算法的内存交换(避免内存碎片);
  • 新生代对象经过多次Minor GC后仍存活,会被“晋升”到老年代。

3.2 核心GC算法(适配不同分代)

GC算法是回收垃圾的底层逻辑,分代收集本质是“不同区域使用不同算法”,3种核心算法的对比的如下,重点掌握“适用区域”和“优缺点”:

GC算法
核心执行流程
核心优点
核心缺点
适用区域
标记-清除
1. 标记所有存活对象;2. 直接清除未标记的垃圾对象
实现简单、无需移动对象,执行速度快
回收后内存碎片严重;清除大量垃圾时效率低
老年代(配合标记-整理算法使用)
标记-复制
1. 标记所有存活对象;2. 将存活对象复制到空闲区域;3. 清空原区域所有对象
无内存碎片;回收效率高(适合存活对象少的场景)
需要预留空闲区域,空间利用率低
新生代(存活对象少,空间浪费可接受)
标记-整理
1. 标记所有存活对象;2. 将存活对象向内存一端移动;3. 清除另一端所有垃圾对象
无内存碎片;无空间浪费,空间利用率高
移动对象耗时久,STW(停顿)时间长
老年代(存活对象多,需避免内存碎片)

核心结论(必记)

新生代用标记-复制算法(因存活对象少,回收效率高);老年代用标记-清除+标记-整理算法(平时用标记-清除提升效率,内存碎片过多时用标记-整理整理内存)。

四、主流GC收集器:算法的工程实现

GC收集器是GC算法的实际工程实现,不同收集器适配不同的业务场景和内存大小。核心分为“分代收集器”(JDK8主流)和“整堆收集器”(JDK9+主流),重点掌握收集器的特点和适用场景。

4.1 收集器核心概念(先理解,再记收集器)

所有收集器都无法完全避免STW(Stop The World,暂停所有用户线程),调优的核心是“减少STW的次数和时长”,先明确4个核心概念:

  • 串行收集:单线程执行GC,全程STW,开销小但效率低;
  • 并行收集:多线程执行GC,STW时间更短,侧重“吞吐量优先”;
  • 并发收集:GC线程与应用线程同时运行,STW时间极短,侧重“响应时间优先”;
  • STW:GC执行时暂停所有用户线程,所有收集器都有STW,仅时长不同。

4.2 经典分代收集器(JDK8主流)

分代收集器需“新生代收集器+老年代收集器”搭配使用,JDK8主流搭配及特点如下:

新生代收集器
老年代收集器
核心特点
适用场景
Serial(串行)
Serial Old(串行老年代)
单线程GC,全程STW,实现简单、开销小,效率低
客户端程序、小内存场景(堆内存<1G,如桌面应用)
ParNew(并行新生代)
CMS(并发老年代)
ParNew多线程GC,CMS并发收集,STW时间短,响应时间优先
Web应用、微服务(堆内存<4G,需低延迟,如接口服务)
Parallel Scavenge(并行 scavenge)
Parallel Old(并行老年代)
多线程GC,全程STW,吞吐量优先(尽可能提升应用运行时间占比)
后台任务、批处理(如数据计算、报表生成,不关注延迟)

注:JDK8默认收集器是「Parallel Scavenge + Parallel Old」,侧重吞吐量。

4.3 G1收集器(JDK9+默认,企业级首选)

G1(Garbage-First)打破了分代边界,是面向大内存、低停顿的整堆收集器,堆内存>4G时首选,核心特点如下(必记):

  1. 堆内存划分:将堆分为多个大小相等的Region(1M~32M),每个Region可动态标记为Eden/Survivor/Old/Humongous(大对象区);
  2. 垃圾优先:优先回收垃圾比例最高的Region(垃圾占比高的Region,回收效率高);
  3. 可预测停顿:支持指定最大GC停顿时间(通过参数-XX:MaxGCPauseMillis=200设置,单位ms);
  4. 无内存碎片:采用标记-复制算法,回收时将存活对象复制到空闲Region,避免碎片。

G1核心回收阶段(简化理解,无需记复杂流程)

  • Young GC:仅回收Eden和Survivor Region,多线程STW,类似新生代GC;
  • Mixed GC:核心回收阶段,回收新生代+部分老年代Region,STW时间可控(符合预设的最大停顿时间);
  • Full GC:保底回收机制(单线程STW,效率极低),需尽量避免触发。

4.4 收集器选择原则(实战必用)

无需死记所有收集器,按内存大小和业务场景选择即可,核心原则:

  1. 小内存(堆<1G):Serial GC(简单高效,无多余开销);
  2. 吞吐量优先(不关注延迟):Parallel GC(JDK8默认,适合批处理);
  3. 响应时间优先(堆<4G):ParNew + CMS(适合Web应用,低延迟);
  4. 响应时间优先(堆>4G):G1 GC(企业级首选,大内存低延迟);
  5. JDK9+:直接使用默认的G1 GC(无需额外配置,适配大多数场景)。

五、GC完整执行流程:理解对象生命周期

以JDK8默认的「Parallel GC」为例,讲解经典分代GC的完整流程,理解对象从创建到回收的全生命周期,重点记“触发条件”和“晋升规则”:

5.1 完整执行流程(按顺序记)

  1. 对象创建:新对象优先在Eden区分配内存;大对象(超过Eden区一半大小)直接进入老年代(避免频繁GC);
  2. Minor GC触发:当Eden区内存满时,触发Minor GC(仅回收新生代),采用标记-复制算法;
  3. 存活对象处理:将Eden区和S0区的存活对象,复制到S1区,然后清空Eden区和S0区,最后S0和S1互换身份(原S1变为新的S0,原S0变为新的S1);
  4. 年龄晋升:每次Minor GC后,存活对象的“年龄”+1(年龄记录在对象头中),当年龄达到阈值(默认15,可通过参数调整),晋升到老年代;
  5. 动态年龄判断:若S1区中,相同年龄的对象总大小超过S1区的50%,则该年龄及以上的所有对象,直接晋升到老年代(无需达到阈值15);
  6. Full GC触发:当老年代内存不足、Minor GC时存活对象无法晋升(老年代无足够空间),触发Full GC,回收新生代+老年代+元空间(STW时间长,需避免)。

5.2 关键概念区分(面试高频)

很多人混淆Minor GC、Major GC、Full GC,明确区分如下:

  • Minor GC:仅回收新生代,频率高(每秒几次),STW时间短(毫秒级),对应用无感知;
  • Major GC:仅回收老年代,频率低(每小时几次),STW时间较长(百毫秒级);
  • Full GC:回收整堆(新生代+老年代)+ 元空间,频率极低(理想状态下几小时/几天一次),STW时间长(秒级),调优核心是减少Full GC。

六、GC调优实战:从监控到落地

GC调优是工程实践,无需追求“最优参数”,核心是“匹配业务场景”。调优完整流程:监控GC日志 → 分析问题 → 调整参数 → 验证效果,循环迭代,直到满足业务需求。

6.1 调优核心原则(黄金法则,必记)

  1. 优先避免Full GC(Full GC的STW时间最长,是性能瓶颈);
  2. 控制Minor GC频率(过高会累计STW时间,如每秒10次,每次10ms,累计100ms/秒);
  3. 适配业务场景(吞吐量优先选Parallel,响应时间优先选G1);
  4. 小步调整参数(每次仅调整1-2个参数,便于验证效果,避免盲目调整);
  5. 拒绝过度调优(满足业务要求即可,如STW≤500ms、Full GC≤1次/天,无需追求极致)。

6.2 核心监控工具(落地必备)

监控是调优的前提,分为“JVM自带工具(轻量无侵入)”和“可视化工具(分析高效)”,重点掌握自带工具的使用(生产环境可用)。

6.2.1 JVM自带工具(命令行,生产环境首选)

# 1. 查看当前运行的JVM进程ID(关键,后续所有命令需用到进程ID)
jps -l  # 输出格式:进程ID  类名/jar包名(如12345 com.example.Application)

# 2. 实时监控GC状态(每1秒打印1次,共打印10次,可调整参数)
jstat -gc 12345 1000 10  # 12345=进程ID,1000=间隔时间(ms),10=打印次数

# 3. 生成堆快照(分析内存泄漏、对象分布,后缀为hprof)
jmap -dump:format=b,file=heap.hprof 12345  # file=堆快照保存路径+文件名

# 4. 查看JVM当前运行时参数(查看是否生效,如堆大小、收集器类型)
jinfo -flags 12345

6.2.2 可视化工具(分析日志/快照,开发/测试环境用)

  • GCViewer:分析GC日志,生成可视化图表(如GC频率、STW时长);
  • MAT(Memory Analyzer Tool):分析堆快照(hprof文件),定位内存泄漏(核心工具);
  • Arthas:阿里开源,线上实时诊断工具(无需重启应用),可监控GC、内存、线程;
  • GC Easy:在线GC日志分析工具(https://gceasy.io/),新手友好,上传日志自动生成分析报告。

6.3 核心JVM参数(落地可用,直接复制配置)

参数无需死记,按场景选择配置,重点掌握“基础堆内存参数”和“G1参数”(企业级常用),所有参数均为JDK8+兼容。

6.3.1 基础堆内存参数(通用,所有场景必配)

# 初始堆内存 = 最大堆内存(避免JVM动态调整堆大小,减少性能损耗)
-Xms4G -Xmx4G  # 根据服务器内存调整,如8G服务器可设为-Xms6G -Xmx6G

# 元空间大小(避免元空间溢出,初始=256M,最大=512M,可按需调整)
-XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=512M

# 新生代Eden:S0:S1比例(默认8:1:1,无需修改,符合分代模型)
-XX:SurvivorRatio=8

# 对象晋升老年代的年龄阈值(默认15,可按需调整,如调整为10)
-XX:MaxTenuringThreshold=15

6.3.2 G1 GC参数(生产环境主流,堆>4G首选)

# 开启G1收集器(JDK9+默认开启,JDK8需手动开启)
-XX:+UseG1GC

# 指定最大GC停顿时间(核心参数,如200ms,根据业务调整)
-XX:MaxGCPauseMillis=200

# 并行GC线程数(匹配CPU核心数,如8核CPU设为8,避免线程切换损耗)
-XX:ParallelGCThreads=8

# 并发GC线程数(一般设为并行线程数的1/2,如4)
-XX:ConcGCThreads=4

# 触发Mixed GC的堆占用阈值(默认45%,设为30%可更早触发,避免Full GC)
-XX:InitiatingHeapOccupancyPercent=30

6.3.3 GC日志参数(必配,用于问题排查)

# 打印GC详细信息、时间戳、日期戳
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps

# GC日志保存路径+文件名(按应用目录配置)
-Xloggc:/usr/local/app/gc.log

# GC日志轮转(避免日志过大,保留5个文件,每个100M)
-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=100M

# OOM时自动生成堆快照(排查OOM原因的关键,必配)
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/app/heap.hprof

6.4 常见GC问题与解决方案(实战高频)

调优的核心是“解决具体问题”,以下是生产环境最常见的4类GC问题,对应解决方案直接落地:

常见问题
核心现象
解决方案(优先执行)
Minor GC频率过高

每秒触发1次以上,YGCT(新生代GC累计时间)过长
1. 增大新生代内存(通过-Xmn调整,如-Xmn2G);2. 优化代码,减少临时对象创建(如避免循环内创建字符串)
Full GC频繁
每小时触发几次,FGCT(Full GC累计时间)长,应用卡顿
1. 增大堆内存(调大-Xms/-Xmx);2. 排查内存泄漏(用MAT分析堆快照);3. 切换为G1 GC(堆>4G时)
CMS收集器碎片严重
老年代有空闲内存,但无法分配大对象,触发Full GC
1. 开启CMS Full GC后整理(-XX:+UseCMSCompactAtFullCollection);2. 切换为G1 GC(彻底解决碎片问题)
G1触发Full GC
Mixed GC回收不及时,老年代占满,触发Full GC
1. 降低InitiatingHeapOccupancyPercent阈值(如从30%调为25%);2. 减少大对象创建(大对象直接进老年代,易占满)

6.5 生产环境参数示例(G1 GC,4G堆,直接复用)

适用于Web应用、微服务(堆内存4G,响应时间优先),可根据服务器内存和业务调整:

# 堆内存配置(初始=最大,避免动态调整) -Xms4G -Xmx4G # G1收集器配置(响应时间优先) -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=8 -XX:ConcGCThreads=4 -XX:InitiatingHeapOccupancyPercent=30 # 元空间配置(避免溢出) -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=1G # 禁止手动GC(避免代码中调用System.gc()触发Full GC) -XX:+DisableExplicitGC # GC日志配置(轮转+详细信息,便于排查) -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:/usr/local/app/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=100M # OOM快照配置(排查OOM关键) -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/app/heap.hprof

6.6 内存泄漏定位(GC调优的关键难点)

内存泄漏是Full GC频繁、OOM的核心原因(对象无法被回收,长期占用内存),先明确常见泄漏场景,再掌握定位流程。

6.6.1 常见内存泄漏场景(必记,避免踩坑)

  • 静态集合未清理:如static List&lt;Object&gt; list = new ArrayList<>(),无限添加对象,且未手动清理;
  • 单例持有外部短生命周期引用:单例对象长期存活,持有Controller、Service等短生命周期对象的引用;
  • 未关闭的资源:数据库连接、文件流、网络连接、线程池,未关闭/销毁,导致资源占用;
  • ThreadLocal使用不当:未及时remove(),线程池复用线程时,ThreadLocal中的对象无法被回收。

6.6.2 内存泄漏定位流程(落地可用)

  1. 用jmap命令生成堆快照(jmap -dump:format=b,file=heap.hprof 进程ID);
  2. 用MAT工具打开堆快照,查看「Leak Suspects」(泄漏可疑点);
  3. 查看「Dominator Tree」(支配树),定位占用内存最多的对象(泄漏对象);
  4. 追溯泄漏对象的引用链,找到未释放引用的代码,优化代码释放引用(如清理静态集合、关闭资源)。

七、总结:核心要点与终极目标

7.1 核心要点回顾(必记,面试/调优都用)

  1. 垃圾判断:JVM通过「可达性分析算法」(GC Roots)判断垃圾,4种引用类型决定回收策略;
  2. 分代模型:堆分为新生代(标记-复制,Minor GC)和老年代(标记-清除+整理,Full GC),核心依据是“对象朝生夕死”;
  3. 收集器选择:小内存用Serial,吞吐量优先用Parallel,大内存低延迟用G1;
  4. 调优核心:开启GC日志→监控状态→匹配业务场景→优先避免Full GC→小步调整验证。

7.2 调优终极目标

GC调优的终极目标不是“消除GC”(Java的自动内存管理必然存在GC),而是让GC的频率和停顿时间,适配业务需求——找到「吞吐量」与「响应时间」的平衡点,确保应用稳定运行,无明显卡顿,就是最优的GC调优方案。

版权所属:SO JSON在线解析

原文地址:https://www.sojson.com/blog/580.html

转载时必须以链接形式注明原始出处及本声明。

本文主题:
GC

如果本文对你有帮助,那么请你赞助我,让我更有激情的写下去,帮助更多的人。

关于作者
一个低调而闷骚的男人。
相关文章
垃圾分类垃圾桶有几种?各代表什么?
垃圾分类怎么分类具体内容
Java 解析码,google.ZXing 讲解
垃圾分类怎么分类具体内容
垃圾分类垃圾桶有几种?各代表什么?
使用zxing解析码抛出com.google.zxing.NotFoundException 解决方案
SQL外连接剖
Java 完美解析.plist & 生成plist ,Android 解析.plist
json 解析与生成工具类 ,JSON操作讲解(附件)
如何解析JSON数据(详细解答)
最新文章
文件上传漏洞与防御 1548
前端构建工具选型指南:Webpack、Vite、Rollup、esbuild 深度对比 494
物联网时代2026年时序数据库选型指南 507
SaaS行业面临AI挑战:从“无限复用”到“灵活适应” 683
神经网络:从构造到模型训练全链路解析 554
一文吃透 Redis 核心存储结构:ziplist、listpack 与哈希表扩容 / 并发查询 982
Linux sudo提权完整指南:从基础用法到生产级安全配置 281
XSS 和 CSRF 的本质区别及开发防御全解析 390
JVM垃圾回收(GC)全维度解析:从原理到调优实战 420
Linux动静态库与ELF加载全解析:从实操制作到底层原理 539
最热文章
免费天气API,天气JSON API,不限次数获取十五天的天气预报 771514
最新MyEclipse8.5注册码,有效期到2020年 (已经更新) 708851
苹果电脑Mac怎么恢复出厂系统?苹果系统怎么重装系统? 679457
Jackson 时间格式化,时间注解 @JsonFormat 用法、时差问题说明 562378
我为什么要选择RabbitMQ ,RabbitMQ简介,各种MQ选型对比 512346
Elasticsearch教程(四) elasticsearch head 插件安装和使用 484468
Jackson 美化输出JSON,优雅的输出JSON数据,格式化输出JSON数据... ... 301586
Java 信任所有SSL证书,HTTPS请求抛错,忽略证书请求完美解决 247158
Elasticsearch教程(一),全程直播(小白级别) 232831
谈谈斐讯路由器劫持,你用斐讯路由器,你需要知道的事情 228099
支付扫码

所有赞助/开支都讲公开明细,用于网站维护:赞助名单查看

查看我的收藏

正在加载... ...