JVM GC垃圾回收初步了解

在了解 JVM之前,给大家推荐一个工具,阿里开源的 Arthas 。官网地址: arthas 。 这个工具功能非常强大,是对 Java进程进行性能调优的一个非常重要的工具,对于了解 JVM 底层帮助也非常大。


一、垃圾回收器核心定义

1.1 什么是GC垃圾回收器?

通俗一句话定义:GC 是 JVM 自带的自动内存清理工具,专门用来回收程序中「没有任何引用、不再使用的无效对象」,自动释放堆内存,避免内存溢出、内存泄漏。

写 Java 不需要像 C/C++ 一样手动申请、释放内存,核心就是依靠 GC 自动管理内存。

GC 的核心工作只有两件事:

  • 标记:找出内存中哪些对象是垃圾、哪些是存活对象

  • 清除:回收无效垃圾对象,腾空内存空间

核心目标:保证内存高效利用、避免内存溢出、保障程序稳定长期运行


二、JVM内存布局(GC的工作场地)

想要学懂 GC,必须先搞懂 GC 到底在哪工作。GC 不是全内存回收,只针对线程共享的动态内存区域工作,线程私有区域几乎不参与GC回收。

2.1 完整内存布局划分(JDK8标准)

JVM 运行时数据区分为两大板块:线程私有内存、线程共享内存。

1. 线程私有区域(无GC、无需垃圾回收)

生命周期和线程绑定,线程创建则创建,线程销毁则自动释放,无需GC干预:

  • 程序计数器:记录代码执行位置,空间极小,无GC、无OOM

  • 虚拟机栈:存储方法栈帧、局部变量,线程结束自动释放

  • 本地方法栈:服务native底层方法,随线程销毁释放

2. 线程共享区域(GC核心工作区)

全局共享、长期驻留、动态创建对象,是GC唯一的工作范围:

  • 堆内存(Heap)GC主战场 ,所有new对象、数组全部存在这里,99%的垃圾回收都发生在堆,他的大小可以由参数 -Xms(初始堆内存大小), -Xmx(最大堆内存)参数指令。

  • 元空间(Metaspace):JDK8替代永久代,存储类元数据、静态信息,极少触发GC

2.2 布局核心总结(GC关键结论)

GC 只管堆内存!

栈内存、程序计数器完全不用GC,线程结束自动释放。所有GC调优、GC卡顿、垃圾回收问题,全部聚焦在堆内存


三、分代收集模型(GC核心设计思想)

3.1 为什么需要分代?

JVM 通过大量统计发现:

JAVA做过统计, 80%的对象都是"朝生夕死" 。这些对象,被集中放在了一块比较小的内存空间当中,快速创建,快速回收,这块内存区域就是年轻代。在年轻代会非常频繁的进行垃圾回收,称为YoungGC。而年轻代又会被进一步划分为一个eden_space和两个survivor。这三个区域的大小比例默认是 8:1:1。

另外少部分需要长期使用的对象,被放到另一块竞争没有那么激烈的对象,则被放到另外一块比较大的内存空间当中,长期保持,这块内存就是老年代。在老年代,垃圾回收的频率则会相对比较低,只有空间不够时才进行,称为OldGC。

年轻代与老年代默认的大小比例是 1:2。

常见的分代收集模型中,对象会优先在eden区创建,经过一次YoungGC后,如果没有被回收,就会被移动到一个survivor区。接下来,下一次YoungGC时,又会被移动到另一块Survivor区。每移动一次,记录一个分代年龄。直到分代年龄太大了(默认是16),就会被移动到老年代。到老年代后,对象就不再记录分代年龄了,在老年代安安静静的用到退休。

这就是JDK最有代表性的分代年龄收集机制。通过分代收集机制, JVM可以对不同的对象采取不同的回收策略,从而提高垃圾回收的效率。

如果所有对象统一回收,效率极低、浪费性能。所以 JVM 把堆内存按对象存活时间分代,不同代采用不同回收策略,大幅提升GC效率。

3.2 堆内存分代结构(JDK8通用)

堆内存整体分为两大代:新生代 + 老年代

1. 新生代(Young Generation)

存放刚创建、短期存活的临时对象,占堆内存 1/3。

内部细分三块:Eden区 + Survive0 + Survive1

  • Eden区:对象新建的默认区域,占新生代80%空间

  • 两个Survivor区:大小相等、轮换使用,用于存活对象转移

特点:对象消亡快、回收频率高、回收速度快。

2. 老年代(Old Generation)

存放长期存活、年龄较大、体积较大的对象,占堆内存 2/3。

经过多次新生代GC依然存活的对象,会晋升到老年代。

特点:对象存活久、回收频率低、单次回收耗时更长。

3.3 分代收集工作机制(完整流程)

全程大白话串联对象的一生,看懂就彻底懂分代GC:

  1. 对象创建:所有新对象优先在 Eden 区诞生

  2. 新生代GC(Minor GC):Eden区满了触发Minor GC,快速清理所有垃圾对象;存活对象移入空闲的Survivor区,年龄+1

  3. 年龄晋升:对象反复躲过多次Minor GC,年龄达到阈值(默认15),晋升到老年代;大对象直接进入老年代

  4. 老年代GC(Major GC/Full GC):老年代空间不足时触发,回收老年代垃圾,速度慢、耗时久、尽量避免频繁触发

核心优势:短命对象快速清理、长命对象少扫描,精准适配对象生命周期,最大化GC效率。


四、JVM主流垃圾回收器大全(JDK8生产常用)

分代模型是底层设计,回收器是具体干活的工具。不同回收器,对应不同回收算法、性能、适用场景。java 从诞生到现在最新的 JDK21版本,总共就产生了以下十个垃圾回收器

JDK8 主流四大回收器,按使用场景、性能优先级排序

4.1 Serial 串行回收器(单线程)

  • 工作方式:单线程回收,回收时暂停所有业务线程(STW)

  • 特点:简单、内存占用小、无多线程竞争、卡顿严重

  • 适用场景:客户端程序、单机小项目、测试环境,生产基本不用

4.2 Parallel 并行回收器(JDK8默认)

  • 工作方式:多线程并行GC,依旧存在STW,优先追求高吞吐量

  • 特点:回收速度快、吞吐量高、适合后台任务、对延迟不敏感

  • 适用场景:普通后台业务、批量任务、非核心高并发服务

4.3 CMS 并发回收器(经典低延迟)

  • 工作方式:大部分阶段和业务线程并发执行,减少STW停顿时间

  • 特点:低延迟、卡顿小、吞吐量略低、内存碎片多

  • 适用场景:需要快速响应、低延迟的核心业务(订单、支付、用户服务)

  • 备注:JDK9开始废弃,JDK8为最后稳定可用版本

4.4 G1 垃圾回收器(高性能全能型)

  • 工作方式:摒弃传统分代固定划分,采用分区回收,可灵活控制停顿时间

  • 特点:兼顾吞吐量与低延迟、可预测卡顿、自动整理内存碎片

  • 适用场景:大内存、高并发、高性能、核心生产集群

  • 备注:JDK9+默认回收器,JDK8可手动开启,是目前主流升级方案

4.5 回收器选型极简总结(生产直接用)

  • 小项目、测试环境 → Parallel

  • 核心低延迟业务 → CMS

  • 大内存、高并发、追求稳定 → G1


五、GC实战分析:运行参数、日志参数、日志解读

理论学完,重点落地实战!线上排查GC卡顿、OOM问题,全靠JVM参数+GC日志分析

5.1 常见GC运行参数(JDK8生产标配)

分为内存参数、GC类型参数、日志输出参数三类,全部是生产常用参数:

1. 核心内存参数
  • -Xms:JVM初始堆内存(生产建议与最大堆一致,避免动态扩容)

  • -Xmx:JVM最大堆内存(防止堆溢出)

  • -Xmn:新生代内存大小,单独控制年轻代空间

  • -XX:MaxMetaspaceSize:元空间最大限制,防止元数据溢出

2. 回收器选型参数
  • -XX:+UseParallelGC:开启Parallel并行回收器(默认)

  • -XX:+UseConcMarkSweepGC:开启CMS低延迟回收器

  • -XX:+UseG1GC:开启G1全能回收器

3. 辅助调优参数
  • -XX:MaxGCPauseMillis:限制GC最大停顿时间(G1核心参数)

  • -XX:+PrintGCDetails:打印GC详细日志

  • -XX:+PrintGCTimeStamps:打印GC时间戳

5.2 常用GC日志输出参数

线上排查问题必须开启日志,核心参数:

  • -XX:+PrintGC: 打印GC信息 类似于-verbose:gc

  • -XX:+PrintGCDetails:输出GC详细信息(内存变化、耗时、代区回收情况)

  • -XX:+PrintGCTimeStamps:配合 -XX:+PrintGC使用。在 GC 中打印时间戳。

  • -XX:PrintHeapAtGC: 打印GC前后的堆栈信息

  • -XX:+PrintGCApplicationStoppedTime:打印STW停顿时间

  • -Xloggc:/xxx/gc.log:指定GC日志输出路径,持久化保存

5.3 实战GC日志分析(零基础秒懂)

我们以最常见的 Minor GC 日志 举例,手把手教你读懂日志核心信息:

示例日志

GC (Allocation Failure) [Eden: 20480K->1024K(22528K), 0.012s] 20480K->2048K(81920K), 0.015s]

逐段通俗解读

  • Allocation Failure:内存分配失败,Eden区已满,触发新生代GC

  • Eden:20480K->1024K:Eden区原有20M内存,回收后只剩1M存活对象

  • 22528K:Eden区总容量

  • 0.012s:新生代GC耗时12毫秒

  • 20480K->2048K(81920K):整体堆内存从20M回收至2M,堆总容量80M

  • 0.015s:本次GC总耗时15毫秒

5.4 日志排查核心结论(实战刚需)

  • 频繁Minor GC:新生代内存过小、短命对象过多,需调大新生代空间

  • 频繁Full GC:老年代内存不足、内存泄漏、大对象过多,严重影响性能

  • GC耗时持续走高:堆内存过大、回收器选型不合适,需更换低延迟回收器


六、全文串联总结

整篇文章逻辑闭环,层层关联一键梳理:

  1. GC定义:JVM自动垃圾回收工具,标记+清除无效对象,自动管理堆内存

  2. 内存布局:GC只针对堆内存,栈内存无需回收;堆分新生代、老年代两大区域

  3. 分代模型:适配对象朝生夕灭特性,新生代高频轻量回收、老年代低频重量级回收

  4. 工作机制:对象诞生于Eden -> 存活进入Survivor -> 年龄达标晋升老年代 -> 空间不足触发GC回收

  5. 回收器选型:Parallel追求吞吐量、CMS追求低延迟、G1兼顾全能稳定

  6. GC实战:通过JVM参数配置内存与回收器,通过GC日志分析内存状态、排查性能问题

相关推荐
心之伊始1 小时前
LangChain4j RAG 实战:Java 后端如何把本地文档接入 Embedding 检索链路
java·架构·源码分析·csdn
~小先生~1 小时前
Python从入门到放弃(一)
开发语言·python
许彰午2 小时前
17_synchronized关键字深度解析
java·开发语言
z落落2 小时前
C# 泛型接口和泛型类+泛型约束
开发语言·c#
阿正的梦工坊2 小时前
【Rust】02-变量、不可变性与基础类型
开发语言·后端·rust
阿正的梦工坊2 小时前
【Rust】08-集合类型、字符串与迭代器入门
开发语言·rust·c#
FuckPatience2 小时前
C# 使用泛型协变将派生类类型替换为基类类型
开发语言·c#
张忠琳2 小时前
【Go 1.26.4】(Part 1) Go 1.26.4 超深度源码分析 — 总体架构与模块全景
开发语言·golang
guygg882 小时前
C# 生成中间带 Logo 头像的二维码
开发语言·c#