目录
[(五)相关 VM 参数](#(五)相关 VM 参数)
[(一)G1 垃圾回收阶段](#(一)G1 垃圾回收阶段)
[(1) 年轻代GC(Young GC)](#(1) 年轻代GC(Young GC))
[(2) 并发标记周期](#(2) 并发标记周期)
[(3) 混合回收(Mixed GC)](#(3) 混合回收(Mixed GC))
[(三)最快的 GC](#(三)最快的 GC)
一、如何判断对象可以回收
(一)引用计数法
给对象中添加一个引用计数器:
- 每当有一个地方引用它,计数器就加 1;
- 当引用失效,计数器就减 1;
- 任何时候计数器为 0 的对象就是不可能再被使用的。
这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间循环引用的问题。

所谓对象之间的相互引用问题,如下面代码所示:除了对象 objA
和 objB
相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。
java
public class ReferenceCountingGc {
Object instance = null;
public static void main(String[] args) {
ReferenceCountingGc objA = new ReferenceCountingGc();
ReferenceCountingGc objB = new ReferenceCountingGc();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
}
}
(二)可达性分析算法
Java通过GC Roots
对象作为起点,向下搜索引用链。若对象与GC Roots
无引用链相连,则判定为可回收。
GC Roots包括:
-
虚拟机栈中引用的对象(如局部变量)
-
方法区中类静态属性引用的对象
-
方法区中常量引用的对象
-
本地方法栈中JNI引用的对象
-
同步锁持有的对象
-
活跃线程对象
-
引用类型与回收策略
引用类型 回收条件 示例 强引用 永不回收(除非显式置为 null
)Object obj = new Object()
软引用(Soft) 内存不足时回收 SoftReference<Object> ref
弱引用(Weak) 下次GC必然回收 WeakReference<Object> ref
虚引用(Phantom) 仅用于回收跟踪 PhantomReference<Object> ref
-
对象死亡过程

- 强引用
- 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
- 软引用(SoftReference)
- 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象
- 可以配合引用队列来释放软引用自身
- 弱引用(WeakReference)
- 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
- 可以配合引用队列来释放弱引用自身
- 虚引用(PhantomReference)
- 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队, 由 Reference Handler 线程调用虚引用相关方法释放直接内存
- 终结器引用(FinalReference)
- 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象 暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象
二、垃圾回收算法
(一)标记清除
定义: Mark Sweep
- 速度较快
- 会造成内存碎片

(二)标记整理
定义:Mark Compact
- 速度慢
- 没有内存碎片

(三)复制
定义:Copy
- 不会有内存碎片
- 需要占用双倍内存空间

(四)分代垃圾回收

-
年轻代(Young Generation)
-
对象分配:新对象在Eden区创建
-
回收算法:复制算法(Minor GC)
-
过程:
-
Eden(伊甸园) + S0(From)存活对象复制到S1(To)
-
清空Eden(伊甸园)和S0(From)
-
S0(From)与S1(To)角色交换
-
-
晋升条件:对象年龄(经历GC次数)达到阈值(默认15),或Survivor区空间不足
-
-
老年代(Old Generation)
-
存放长期存活对象
-
回收算法:标记-清除或标记-整理(Major GC/Full GC)
-
触发条件:年轻代晋升失败或空间不足
-
- 对象首先分配在伊甸园区域
- 新生代空间不足时,触发 Minor GC,伊甸园和 From 存活的对象使用copy复制到 To 中,存活的对象年龄加1并且交换 From 和 To
- Minor GC 会引发 Stop The World,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
- 当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)
- 当老年代空间不足,会先尝试触发 Minor GC,如果之后空间仍不足,那么触发 Full GC,STW的时间更长
算法 | 原理 | 优点 | 缺点 |
---|---|---|---|
标记-清除 | 1. 标记所有活动对象 2. 清除未标记对象 | 实现简单 | 内存碎片化,分配效率低 |
复制 | 内存分为两块,每次使用一块。GC时将存活对象复制到另一块,清空当前块。 | 无碎片,高效 | 内存利用率仅50% |
标记-整理 | 1. 标记活动对象 2. 将存活对象向内存一端移动 3. 清理边界外内存 | 无碎片,内存利用率高 | 对象移动开销大 |
分代收集 | 结合多种算法,按对象生命周期分区管理 | 综合性能最优(主流方案) | 实现复杂 |
(五)相关 VM 参数
含义 | 参数 |
---|---|
堆初始大小 | -Xms |
堆最大大小 | -Xmx 或 -XX:MaxHeapSize=size |
新生代大小 | -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size ) |
幸存区比例(动态) | -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy |
幸存区比例 | -XX:SurvivorRatio=ratio |
晋升阈值 | -XX:MaxTenuringThreshold=threshold |
晋升详情 | -XX:+PrintTenuringDistribution |
GC详情 | -XX:+PrintGCDetails -verbose:gc |
FullGC 前 MinorGC | -XX:+ScavengeBeforeFullGC |
三、垃圾回收器
- 串行(Serial)
- 单线程
- 堆内存较小,适合个人电脑
- 吞吐量优先(Parallel Scavenge)
- 多线程
- 堆内存较大,多核 cpu
- 让单位时间内,STW 的时间最短 0.2 0.2 = 0.4,垃圾回收时间占比最低,这样就称吞吐量高
- 响应时间优先(CMS)
- 多线程
- 堆内存较大,多核 cpu
- 尽可能让单次 STW 的时间最短 0.1 0.1 0.1 0.1 0.1 = 0.5
(一)串行(Serial)
-XX:+UseSerialGC = Serial + SerialOld

(二)吞吐量优先(Parallel Scavenge)
-XX:+UseParallelGC ~ -XX:+UseParallelOldGC
-XX:+UseAdaptiveSizePolicy
-XX:GCTimeRatio=ratio
-XX:MaxGCPauseMillis=ms
-XX:ParallelGCThreads=n

(三)响应时间优先(CMS)
-XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld
-XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads
-XX:CMSInitiatingOccupancyFraction=percent
-XX:+CMSScavengeBeforeRemark

(四)G1
G1 (Garbage-First) 是Java 7引入、Java 9成为默认的垃圾回收器,专为大内存、低延迟场景设计,替代了传统的CMS回收器。
核心特点
-
分区模型:将堆划分为多个等大小的Region(默认约2048个)
-
分代收集:保留新生代/老年代概念,但物理不连续
-
可预测停顿 :通过
-XX:MaxGCPauseMillis
设置目标停顿时间 -
并发标记:与应用程序线程并行工作
-
混合回收:同时清理新生代和老年代区域
(一)G1 垃圾回收阶段

Young Collection
- 会 STW



Young Collection + CM
- 在 Young GC 时会进行 GC Root 的初始标记
- 老年代占用堆空间比例达到阈值时,进行并发标记(不会 STW),由下面的 JVM 参数决定
-XX:InitiatingHeapOccupancyPercent=percent (默认45%)

Mixed Collection
会对 E、S、O 进行全面垃圾回收
- 最终标记(Remark)会 STW
- 拷贝存活(Evacuation)会 STW
-XX:MaxGCPauseMillis=ms

(二)核心阶段详解
(1) 年轻代GC(Young GC)
-
触发条件:Eden区满时
-
过程:
-
STW(Stop-The-World)开始
-
构造回收集(Collection Set = Eden + Survivor)
-
复制存活对象到新Survivor区
-
年龄达到阈值(默认15)的对象晋升老年代
-
(2) 并发标记周期
-
触发条件:老年代占用超过IHOP阈值(默认45%)
-
阶段组成:
-
初始标记(STW):伴随Young GC进行
-
根区域扫描:扫描Survivor区引用
-
并发标记:标记存活对象(与应用并发)
-
最终标记(STW):处理SATB缓冲区
-
清理(STW):统计完全空闲Region
-
(3) 混合回收(Mixed GC)
-
回收包含年轻代Region + 高收益的老年代Region
-
根据垃圾比例排序Region(Garbage-First原则)
-
多次执行直到满足回收目标
(三)G1关键调优参数
基础配置
参数 | 默认值 | 说明 |
---|---|---|
-XX:+UseG1GC |
- | 启用G1回收器 |
-Xms / -Xmx |
- | 堆初始/最大大小(建议设相同值) |
-XX:MaxGCPauseMillis |
200ms | 目标最大停顿时间 |
区域配置
参数 | 默认值 | 说明 |
---|---|---|
-XX:G1HeapRegionSize |
1-32MB | Region大小(2的幂次) |
-XX:G1NewSizePercent |
5% | 新生代最小占比 |
-XX:G1MaxNewSizePercent |
60% | 新生代最大占比 |
并发周期控制
参数 | 默认值 | 说明 |
---|---|---|
-XX:InitiatingHeapOccupancyPercent |
45% | IHOP阈值(触发并发标记) |
-XX:G1MixedGCLiveThresholdPercent |
85% | Region存活对象低于此值才回收 |
-XX:G1HeapWastePercent |
5% | 可回收垃圾占比阈值(停止Mixed GC) |
四、垃圾回收调优
(一)调优领域
- 内存
- 锁竞争
- cpu
- 占用 io
(二)确定目标
- 【低延迟】还是【高吞吐量】,选择合适的回收器
- CMS,G1,ZGC
- ParallelGC
- Zing
(三)最快的 GC
答案是不发生 GC
查看 FullGC 前后的内存占用,考虑下面几个问题
数据是不是太多?
- resultSet = statement.executeQuery("select * from 大表 limit n")
数据表示是否太臃肿?
- 对象图
- 对象大小 16 Integer 24 int 4
是否存在内存泄漏?
- static Map map =
- 软
- 弱
- 第三方缓存实现
(四)新生代调优
-
新生代的特点
- 所有的 new 操作的内存分配非常廉价
- TLAB thread - local allocation buffer
- 死亡对象的回收代价是零
- 大部分对象用过即死
- Minor GC 的时间远远低于 Full GC
- 所有的 new 操作的内存分配非常廉价
-
越大越好吗?

-
新生代能容纳所有【并发量*(请求 - 响应)】的数据
-
幸存区大到能保留【当前活跃对象 + 需要晋升对象】
-
晋升阈值配置得当,让长时间存活对象尽快晋升
- -XX:MaxTenuringThreshold = threshold
- -XX:+PrintTenuringDistribution
javaDesired survivor size 48286924 bytes, new threshold 10 (max 10) age 1: 28992024 bytes, 28992024 total age 2: 1366864 bytes, 30358888 total age 3: 1425912 bytes, 31784800 total ...
(五)老年代调优
以 CMS 为例
- CMS 的老年代内存越大越好
- 先尝试不做调优,如果没有 Full GC 那么已经...,否则先尝试调优新生代
- 观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3
- -XX:CMSInitiatingOccupancyFraction=percent