垃圾收集详解
一、知识概述
垃圾收集(Garbage Collection,GC)是Java自动内存管理的核心机制。JVM会自动回收不再使用的对象占用的内存,开发者无需手动释放内存。理解垃圾收集原理对于性能优化和问题排查至关重要。
垃圾收集要解决的问题
- 哪些内存需要回收? - 判断对象是否存活
- 什么时候回收? - GC触发时机
- 如何回收? - GC算法和收集器选择
垃圾收集发展历程
┌─────────────────────────────────────────────────────────────────────┐
│ Java垃圾收集器发展历程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1999 JDK 1.3 Serial GC, Parallel GC │
│ │ │
│ 2002 JDK 1.4.2 CMS (Concurrent Mark Sweep) │
│ │ │
│ 2006 JDK 6 G1 GC (Garbage First) │
│ │ │
│ 2017 JDK 9 G1成为默认GC │
│ │ │
│ 2018 JDK 11 ZGC (可伸缩低延迟) │
│ │ │
│ 2019 JDK 12 Shenandoah GC │
│ │ │
│ 2021 JDK 17 ZGC改进,分代ZGC │
│ │ │
│ 2023 JDK 21 分代ZGC正式版 │
│ │
└─────────────────────────────────────────────────────────────────────┘
目标演进:从"简单回收"到"低延迟、高吞吐、大内存支持"
二、知识点详细讲解
2.1 对象存活判定
2.1.1 引用计数法
java
/**
* 引用计数法示例
*
* 原理:对象被引用时计数+1,引用失效时计数-1
* 计数为0时回收对象
*
* 优点:实现简单,判定效率高
* 缺点:无法解决循环引用问题
*/
public class ReferenceCountingDemo {
public static void main(String[] args) {
// 循环引用示例
ReferenceObject objA = new ReferenceObject();
ReferenceObject objB = new ReferenceObject();
// 相互引用
objA.reference = objB;
objB.reference = objA;
// 外部引用断开
objA = null;
objB = null;
/*
引用计数法无法回收:
┌──────────┐ ┌──────────┐
│ objA │ │ objB │
│ count=1 │◄────────►│ count=1 │
│ │ │ │
└──────────┘ └──────────┘
外部引用断开后,两者互相引用
计数都不为0,无法回收
导致内存泄漏
*/
System.out.println("引用计数法无法解决循环引用问题");
System.out.println("主流JVM不使用此方法");
}
static class ReferenceObject {
Object reference;
}
}
2.1.2 可达性分析算法
java
/**
* 可达性分析算法
*
* 原理:从GC Roots出发,遍历对象引用链
* 不可达的对象即为垃圾对象
*
* HotSpot JVM使用此方法
*/
public class ReachabilityAnalysisDemo {
public static void main(String[] args) {
System.out.println("=== 可达性分析算法 ===\n");
/*
GC Roots对象类型:
1. 虚拟机栈中引用的对象
- 方法中的局部变量、参数
2. 方法区中类静态属性引用的对象
- static修饰的引用变量
3. 方法区中常量引用的对象
- final修饰的引用变量
4. 本地方法栈中JNI引用的对象
- Native方法引用的对象
5. Java虚拟机内部的引用
- 基本类型的Class对象
- 常驻的异常对象
- 系统类加载器
6. 同步锁持有的对象
- synchronized锁定的对象
7. 反映虚拟机内部情况的JMXBean
*/
reachableObjectDemo();
unreachableObjectDemo();
}
/**
* 可达对象示例
*/
private static void reachableObjectDemo() {
System.out.println("【可达对象】");
// 局部变量在栈中,是GC Root
Object obj = new Object(); // obj可达
// 静态变量是GC Root
StaticHolder.holder = new Object(); // 可达
// 正在运行的线程是GC Root
Thread thread = new Thread(() -> {
Object threadLocal = new Object(); // 线程栈中,可达
});
thread.start();
System.out.println("从GC Roots可到达的对象不会被回收");
System.out.println();
}
/**
* 不可达对象示例
*/
private static void unreachableObjectDemo() {
System.out.println("【不可达对象】");
Object obj = new Object();
obj = null; // 断开引用,对象不可达
// 循环引用但不可达
Node node1 = new Node();
Node node2 = new Node();
node1.next = node2;
node2.next = node1;
// 外部引用断开
node1 = null;
node2 = null;
// 虽然互相引用,但从GC Roots不可达,可被回收
System.out.println("从GC Roots不可达的对象会被回收");
System.out.println("即使存在循环引用也能正确回收");
System.out.println();
/*
可达性分析示例:
GC Roots
│
▼
┌─────┐
│ A │
└──┬──┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌─────┐ ┌─────┐ ┌─────┐
│ B │ │ C │ │ D │
└─────┘ └──┬──┘ └─────┘
│
┌───────────┼
▼ ▼
┌─────┐ ┌─────┐
│ E │ │ F │◄───┐
└─────┘ └─────┘ │
│
┌─────┐ ┌─────┐ │
│ X │───►│ Y │────┘
└─────┘ └─────┘
A-F: 可达,不回收
X-Y: 不可达(即使有循环引用),回收
*/
}
static class StaticHolder {
static Object holder;
}
static class Node {
Node next;
}
}
2.1.3 Java引用类型
java
import java.lang.ref.*;
/**
* Java四种引用类型
*
* 引用强度:强 > 软 > 弱 > 虚
*/
public class ReferenceTypeDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Java四种引用类型 ===\n");
// 1. 强引用
strongReference();
// 2. 软引用
softReference();
// 3. 弱引用
weakReference();
// 4. 虚引用
phantomReference();
}
/**
* 强引用(Strong Reference)
* 最常见的引用类型
* 只要存在强引用,垃圾收集器永远不会回收
*/
private static void strongReference() {
System.out.println("【1. 强引用】");
Object obj = new Object(); // 强引用
// obj指向的对象永远不会被回收,直到引用失效
obj = null; // 引用失效,对象可被回收
System.out.println("强引用: 永不回收,除非引用失效");
System.out.println("代码: Object obj = new Object()");
System.out.println();
}
/**
* 软引用(Soft Reference)
* 内存不足时会被回收
* 适合缓存场景
*/
private static void softReference() throws InterruptedException {
System.out.println("【2. 软引用】");
// 创建软引用
SoftReference<byte[]> softRef = new SoftReference<>(new byte[1024 * 1024]);
System.out.println("软引用对象: " + softRef.get());
// 触发GC(不一定回收软引用)
System.gc();
Thread.sleep(100);
System.out.println("GC后: " + softRef.get()); // 通常还在
// 内存不足时会自动回收软引用
System.out.println("内存不足时会自动回收");
System.out.println("适用场景: 缓存、图片缓存");
System.out.println();
/*
软引用特点:
- 内存充足时保留
- 内存不足时回收
- 适合实现内存敏感的缓存
*/
}
/**
* 弱引用(Weak Reference)
* 无论内存是否充足,GC时都会回收
*/
private static void weakReference() throws InterruptedException {
System.out.println("【3. 弱引用】");
WeakReference<byte[]> weakRef = new WeakReference<>(new byte[1024 * 1024]);
System.out.println("弱引用对象: " + weakRef.get());
// GC后弱引用会被回收
System.gc();
Thread.sleep(100);
System.out.println("GC后: " + weakRef.get()); // 通常为null
System.out.println("适用场景: WeakHashMap、ThreadLocal");
System.out.println();
// WeakHashMap示例
WeakHashMap<Object, String> weakMap = new WeakHashMap<>();
Object key = new Object();
weakMap.put(key, "value");
System.out.println("WeakHashMap size: " + weakMap.size());
key = null; // 断开强引用
System.gc();
Thread.sleep(100);
System.out.println("GC后WeakHashMap size: " + weakMap.size()); // 可能0
System.out.println();
}
/**
* 虚引用(Phantom Reference)
* 无法通过虚引用获取对象
* 用于跟踪对象被垃圾回收的活动
*/
private static void phantomReference() throws InterruptedException {
System.out.println("【4. 虚引用】");
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
PhantomReference<byte[]> phantomRef =
new PhantomReference<>(new byte[1024 * 1024], queue);
System.out.println("虚引用对象: " + phantomRef.get()); // 总是null
System.gc();
Thread.sleep(100);
// 检查引用队列
Reference<? extends byte[]> ref = queue.poll();
if (ref != null) {
System.out.println("对象已被回收,虚引用加入队列");
}
System.out.println("适用场景: 堆外内存清理、资源清理");
System.out.println("特点: 无法获取对象,仅用于回收通知");
System.out.println();
/*
引用队列(ReferenceQueue):
- 软引用、弱引用、虚引用都可以关联队列
- 对象被回收后,引用对象加入队列
- 可通过队列得知哪些对象被回收
*/
}
/**
* 引用类型对比总结
*/
private static void summary() {
System.out.println("\n=== 引用类型对比 ===\n");
System.out.println("┌────────────┬──────────────┬────────────────────┐");
System.out.println("│ 引用类型 │ 回收时机 │ 使用场景 │");
System.out.println("├────────────┼──────────────┼────────────────────┤");
System.out.println("│ 强引用 │ 引用失效 │ 普通对象 │");
System.out.println("│ 软引用 │ 内存不足 │ 缓存 │");
System.out.println("│ 弱引用 │ GC时 │ WeakHashMap │");
System.out.println("│ 虚引用 │ 对象回收后 │ 资源清理 │");
System.out.println("└────────────┴──────────────┴────────────────────┘");
}
}
2.2 垃圾收集算法
2.2.1 标记-清除算法(Mark-Sweep)
java
/**
* 标记-清除算法
*
* 分为两个阶段:
* 1. 标记:标记所有需要回收的对象
* 2. 清除:回收被标记的对象
*/
public class MarkSweepDemo {
public static void main(String[] args) {
System.out.println("=== 标记-清除算法 ===\n");
/*
执行过程:
标记前:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ C │ D │ E │ F │ G │ H │
└───┴───┴───┴───┴───┴───┴───┴───┘
↑ ↑ ↑ ↑ ↑
存活 垃圾 存活 垃圾 垃圾
标记后:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ A │×B │ C │×D │ E │×F │×G │ H │
└───┴───┴───┴───┴───┴───┴───┴───┘
清除后:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ │ C │ │ E │ │ │ H │
└───┴───┴───┴───┴───┴───┴───┴───┘
↑ ↑ ↑ ↑
空闲 空闲 空闲 空闲
*/
System.out.println("优点:");
System.out.println(" - 实现简单");
System.out.println(" - 不需要移动对象");
System.out.println();
System.out.println("缺点:");
System.out.println(" - 执行效率不稳定(对象多时效率低)");
System.out.println(" - 内存碎片问题(不连续)");
System.out.println(" - 分配大对象时可能失败");
System.out.println();
System.out.println("适用场景:");
System.out.println(" - 老年代(对象存活率高)");
System.out.println(" - CMS收集器");
}
}
2.2.2 标记-复制算法(Mark-Copy)
java
/**
* 标记-复制算法
*
* 将内存分为两块,每次只用一块
* GC时将存活对象复制到另一块,清空当前块
*/
public class MarkCopyDemo {
public static void main(String[] args) {
System.out.println("=== 标记-复制算法 ===\n");
/*
执行过程:
GC前:
┌─────────────────────────┬─────────────────────────┐
│ 使用区(From) │ 空闲区(To) │
│ A B C D E F G H │ │
└─────────────────────────┴─────────────────────────┘
↑ ↑ ↑ ↑
存活 垃圾 存活 垃圾
标记复制中:
┌─────────────────────────┬─────────────────────────┐
│ 使用区(From) │ 空闲区(To) │
│ A B C D E F G H │ A C E │
└─────────────────────────┴─────────────────────────┘
复制存活对象
GC后:
┌─────────────────────────┬─────────────────────────┐
│ 空闲区 │ 使用区 │
│ │ A C E │
└─────────────────────────┴─────────────────────────┘
交换角色,清空原From区
*/
System.out.println("优点:");
System.out.println(" - 没有内存碎片");
System.out.println(" - 分配简单(指针碰撞)");
System.out.println(" - 效率高(存活少时)");
System.out.println();
System.out.println("缺点:");
System.out.println(" - 内存利用率低(50%)");
System.out.println(" - 存活多时效率低");
System.out.println(" - 需要复制开销");
System.out.println();
System.out.println("优化:Appel式回收");
System.out.println(" - Eden : Survivor : Survivor = 8 : 1 : 1");
System.out.println(" - 内存利用率 90%");
System.out.println(" - Survivor不够时使用老年代担保");
System.out.println();
System.out.println("适用场景:");
System.out.println(" - 新生代(对象存活率低)");
System.out.println(" - Serial、ParNew、Parallel Scavenge收集器");
}
}
2.2.3 标记-整理算法(Mark-Compact)
java
/**
* 标记-整理算法
*
* 标记后,将存活对象向一端移动,然后清理边界外的内存
*/
public class MarkCompactDemo {
public static void main(String[] args) {
System.out.println("=== 标记-整理算法 ===\n");
/*
执行过程:
标记前:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ C │ D │ E │ F │ G │ H │
└───┴───┴───┴───┴───┴───┴───┴───┘
↑ ↑ ↑ ↑ ↑ ↑
存活 垃圾 存活 垃圾 垃圾 存活
标记后:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ A │×B │ C │×D │ E │×F │×G │ H │
└───┴───┴───┴───┴───┴───┴───┴───┘
整理后:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ C │ E │ H │ | | | │
└───┴───┴───┴───┴───┴───┴───┴───┘
存活对象移动到一端,连续空间
*/
System.out.println("优点:");
System.out.println(" - 没有内存碎片");
System.out.println(" - 内存利用率高");
System.out.println(" - 适合大对象分配");
System.out.println();
System.out.println("缺点:");
System.out.println(" - 移动对象成本高");
System.out.println(" - 需要更新引用");
System.out.println(" - STW时间长");
System.out.println();
System.out.println("适用场景:");
System.out.println(" - 老年代");
System.out.println(" - Serial Old、Parallel Old收集器");
}
}
2.3 垃圾收集器
java
/**
* 垃圾收集器详解
*
* 不同年代使用不同的收集器
*/
public class GarbageCollectorsDemo {
public static void main(String[] args) {
System.out.println("╔════════════════════════════════════════════════════════════╗");
System.out.println("║ 垃圾收集器组合 ║");
System.out.println("╚════════════════════════════════════════════════════════════╝");
System.out.println();
/*
┌─────────────────────────────────────────────────────────────────┐
│ 垃圾收集器组合 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 新生代 老年代 │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Serial │ ────────► │ Serial Old │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ParNew │ ────────► │ CMS │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Parallel Scavenge│ ───────► │ Parallel Old │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────┐ │
│ │ G1 GC │ │
│ │ (不区分年代) │ │
│ └───────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────┐ │
│ │ ZGC │ │
│ │ (不区分年代) │ │
│ └───────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
*/
// 各收集器详解
serialGC();
parallelGC();
cmsGC();
g1GC();
zgc();
}
/**
* Serial GC
* 单线程垃圾收集器
*/
private static void serialGC() {
System.out.println("=== Serial GC ===\n");
System.out.println("特点:");
System.out.println(" - 单线程执行");
System.out.println(" - 简单高效");
System.out.println(" - STW时间长");
System.out.println();
System.out.println("新生代: Serial (复制算法)");
System.out.println("老年代: Serial Old (标记-整理算法)");
System.out.println();
System.out.println("适用场景:");
System.out.println(" - 单核CPU环境");
System.out.println(" - 小内存应用(<100MB)");
System.out.println(" - 客户端模式");
System.out.println();
System.out.println("参数: -XX:+UseSerialGC");
System.out.println();
}
/**
* Parallel GC (Throughput GC)
* 多线程垃圾收集器,注重吞吐量
*/
private static void parallelGC() {
System.out.println("=== Parallel GC ===\n");
System.out.println("特点:");
System.out.println(" - 多线程并行执行");
System.out.println(" - 注重吞吐量");
System.out.println(" - JDK 8默认收集器");
System.out.println();
System.out.println("新生代: Parallel Scavenge (复制算法)");
System.out.println("老年代: Parallel Old (标记-整理算法)");
System.out.println();
System.out.println("吞吐量 = 运行用户代码时间 / (运行用户代码时间 + GC时间)");
System.out.println();
System.out.println("适用场景:");
System.out.println(" - 多核CPU环境");
System.out.println(" - 后台计算任务");
System.out.println(" - 批处理应用");
System.out.println();
System.out.println("参数:");
System.out.println(" -XX:+UseParallelGC");
System.out.println(" -XX:MaxGCPauseMillis=200 最大停顿时间");
System.out.println(" -XX:GCTimeRatio=99 吞吐量目标");
System.out.println(" -XX:ParallelGCThreads=N GC线程数");
System.out.println();
}
/**
* CMS GC (Concurrent Mark Sweep)
* 注重低延迟的收集器
*/
private static void cmsGC() {
System.out.println("=== CMS GC ===\n");
System.out.println("特点:");
System.out.println(" - 并发收集,低停顿");
System.out.println(" - 标记-清除算法");
System.out.println(" - JDK 14已移除");
System.out.println();
System.out.println("四个阶段:");
System.out.println(" 1. 初始标记 (STW) - 标记GC Roots直接关联的对象");
System.out.println(" 2. 并发标记 - 遍历对象图(与用户线程并发)");
System.out.println(" 3. 重新标记 (STW) - 修正并发标记期间的变动");
System.out.println(" 4. 并发清除 - 清除垃圾对象(与用户线程并发)");
System.out.println();
System.out.println("优点:");
System.out.println(" - 并发收集,停顿时间短");
System.out.println(" - 用户体验好");
System.out.println();
System.out.println("缺点:");
System.out.println(" - CPU敏感(占用线程资源)");
System.out.println(" - 浮动垃圾(并发清理时产生)");
System.out.println(" - 内存碎片(标记-清除)");
System.out.println();
System.out.println("适用场景:");
System.out.println(" - 互联网应用");
System.out.println(" - 注重响应时间");
System.out.println();
System.out.println("参数:");
System.out.println(" -XX:+UseConcMarkSweepGC");
System.out.println(" -XX:CMSInitiatingOccupancyFraction=75");
System.out.println(" -XX:+UseCMSCompactAtFullCollection");
System.out.println();
}
/**
* G1 GC (Garbage First)
* 面向服务端的收集器
*/
private static void g1GC() {
System.out.println("=== G1 GC ===\n");
System.out.println("特点:");
System.out.println(" - 基于Region的内存布局");
System.out.println(" - 可预测停顿时间模型");
System.out.println(" - JDK 9+默认收集器");
System.out.println();
System.out.println("Region划分:");
System.out.println(" ┌─────┬─────┬─────┬─────┬─────┐");
System.out.println(" │ E │ E │ E │ S │ S │");
System.out.println(" ├─────┼─────┼─────┼─────┼─────┤");
System.out.println(" │ O │ O │ O │ O │ O │");
System.out.println(" ├─────┼─────┼─────┼─────┼─────┤");
System.out.println(" │ H │ H │ O │ O │ O │");
System.out.println(" └─────┴─────┴─────┴─────┴─────┘");
System.out.println(" E: Eden S: Survivor O: Old H: Humongous");
System.out.println();
System.out.println("工作模式:");
System.out.println(" 1. Young GC - Eden区满时触发");
System.out.println(" 2. Mixed GC - 回收部分老年代Region");
System.out.println(" 3. Full GC - 回收失败时触发(应避免)");
System.out.println();
System.out.println("关键特性:");
System.out.println(" - 可预测停顿: -XX:MaxGCPauseMillis=200");
System.out.println(" - 增量回收: 每次只回收部分Region");
System.out.println(" - 无内存碎片: 整体复制,局部标记-整理");
System.out.println();
System.out.println("适用场景:");
System.out.println(" - 大堆内存(>4GB)");
System.out.println(" - 多核CPU");
System.out.println(" - 需要可预测停顿时间");
System.out.println();
System.out.println("参数:");
System.out.println(" -XX:+UseG1GC");
System.out.println(" -XX:MaxGCPauseMillis=200");
System.out.println(" -XX:G1HeapRegionSize=2m");
System.out.println(" -XX:InitiatingHeapOccupancyPercent=45");
System.out.println();
}
/**
* ZGC (Z Garbage Collector)
* 低延迟垃圾收集器
*/
private static void zgc() {
System.out.println("=== ZGC ===\n");
System.out.println("特点:");
System.out.println(" - 亚毫秒级停顿(<10ms)");
System.out.println(" - 支持16TB堆内存");
System.out.println(" - JDK 15正式可用");
System.out.println(" - JDK 21分代ZGC");
System.out.println();
System.out.println("关键技术:");
System.out.println(" - 读屏障: 并发标记和移动");
System.out.println(" - 染色指针: 标记对象状态");
System.out.println(" - 多重映射: 虚拟内存映射");
System.out.println();
System.out.println("三个阶段:");
System.out.println(" 1. 标记 (并发) - 标记存活对象");
System.out.println(" 2. 转移 (并发) - 移动存活对象");
System.out.println(" 3. 重定位 (并发) - 更新引用");
System.out.println();
System.out.println("优点:");
System.out.println(" - 极低延迟");
System.out.println(" - 支持超大堆");
System.out.println(" - 吞吐量影响小");
System.out.println();
System.out.println("适用场景:");
System.out.println(" - 大堆内存(>16GB)");
System.out.println(" - 对延迟敏感的应用");
System.out.println(" - 金融交易系统");
System.out.println();
System.out.println("参数:");
System.out.println(" -XX:+UseZGC");
System.out.println(" -XX:ZCollectionInterval=5 触发间隔");
System.out.println(" -XX:ZAllocationSpikeTolerance=2");
System.out.println(" JDK 21:");
System.out.println(" -XX:+UseZGC -XX:+ZGenerational 分代ZGC");
System.out.println();
}
}
2.4 GC日志分析
java
/**
* GC日志分析示例
*/
public class GCLogAnalysis {
public static void main(String[] args) {
System.out.println("=== GC日志配置与分析 ===\n");
printGCLogConfig();
parseGCLogExample();
}
/**
* GC日志配置
*/
private static void printGCLogConfig() {
System.out.println("【GC日志配置参数】\n");
System.out.println("JDK 8及之前:");
System.out.println(" -XX:+PrintGC 打印GC信息");
System.out.println(" -XX:+PrintGCDetails 打印详细GC信息");
System.out.println(" -XX:+PrintGCTimeStamps 打印时间戳");
System.out.println(" -XX:+PrintGCDateStamps 打印日期时间");
System.out.println(" -XX:+PrintGCApplicationStoppedTime 停顿时间");
System.out.println(" -Xloggc:gc.log 输出到文件");
System.out.println();
System.out.println("JDK 9及之后:");
System.out.println(" -Xlog:gc 基本GC日志");
System.out.println(" -Xlog:gc* 详细GC日志");
System.out.println(" -Xlog:gc*:file=gc.log 输出到文件");
System.out.println(" -Xlog:gc*:gc.log:time,uptime 时间戳");
System.out.println(" -Xlog:gc*:gc.log:time,level,tags 完整信息");
System.out.println();
System.out.println("生产环境推荐配置:");
System.out.println("JDK 8:");
System.out.println(" -XX:+PrintGCDetails -XX:+PrintGCDateStamps \\");
System.out.println(" -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime \\");
System.out.println(" -Xloggc:/var/log/gc.log -XX:+UseGCLogFileRotation \\");
System.out.println(" -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=10M");
System.out.println();
System.out.println("JDK 11+:");
System.out.println(" -Xlog:gc*,gc+heap=debug,gc+age=trace:file=/var/log/gc.log:time,uptime,level,tags:filecount=10,filesize=10m");
System.out.println();
}
/**
* GC日志解析示例
*/
private static void parseGCLogExample() {
System.out.println("【GC日志解析示例】\n");
// Minor GC示例
String minorGC = """
[GC (Allocation Failure) [PSYoungGen: 65536K->8192K(76288K)] 65536K->8296K(251392K), 0.0154567 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]
""";
System.out.println("Minor GC日志:");
System.out.println(minorGC);
System.out.println("解析:");
System.out.println(" 原因: Allocation Failure (分配失败)");
System.out.println(" 新生代: 65536K -> 8192K (总大小76288K)");
System.out.println(" 整堆: 65536K -> 8296K (总大小251392K)");
System.out.println(" 耗时: 0.015秒");
System.out.println();
// Full GC示例
String fullGC = """
[Full GC (Ergonomics) [PSYoungGen: 8192K->0K(76288K)] [ParOldGen: 104K->7856K(175104K)] 8296K->7856K(251392K), [Metaspace: 3456K->3456K(1056768K)], 0.1234567 secs] [Times: user=0.35 sys=0.01, real=0.12 secs]
""";
System.out.println("Full GC日志:");
System.out.println(fullGC);
System.out.println("解析:");
System.out.println(" 原因: Ergonomics (自适应调节)");
System.out.println(" 新生代: 8192K -> 0K");
System.out.println(" 老年代: 104K -> 7856K");
System.out.println(" 元空间: 3456K -> 3456K");
System.out.println(" 整堆: 8296K -> 7856K");
System.out.println(" 耗时: 0.123秒");
System.out.println();
// G1 GC示例
String g1GC = """
[GC pause (G1 Evacuation Pause) (young), 0.0123456 secs]
[Eden: 100.0M(100.0M)->0.0B(80.0M) Survivors: 10.0M->15.0M Heap: 150.0M(256.0M)->50.0M(256.0M)]
""";
System.out.println("G1 GC日志:");
System.out.println(g1GC);
System.out.println("解析:");
System.out.println(" 类型: G1 Evacuation Pause (young)");
System.out.println(" Eden: 100M -> 0M");
System.out.println(" Survivor: 10M -> 15M");
System.out.println(" 堆: 150M -> 50M");
System.out.println(" 耗时: 0.012秒");
System.out.println();
// GC日志分析工具
System.out.println("【GC日志分析工具】");
System.out.println(" 1. GCViewer: 开源可视化工具");
System.out.println(" 2. GCEasy: 在线分析 https://gceasy.io");
System.out.println(" 3. GCPlot: 分布式GC日志分析");
System.out.println(" 4. JVisualVM: JDK自带监控工具");
System.out.println(" 5. JConsole: JDK自带监控工具");
}
}
三、可运行Java代码示例
完整示例:GC行为演示
java
import java.util.*;
import java.lang.management.*;
/**
* GC行为演示
*/
public class GCDemo {
private static final Runtime runtime = Runtime.getRuntime();
public static void main(String[] args) throws InterruptedException {
System.out.println("=== GC行为演示 ===\n");
printMemoryInfo("初始状态");
// 1. 创建对象触发Minor GC
System.out.println("\n【1. 创建对象触发Minor GC】");
List<byte[]> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add(new byte[1024 * 1024]); // 1MB
if (i % 20 == 0) {
printMemoryInfo("已分配 " + (i + 1) + "MB");
}
}
printMemoryInfo("分配100MB后");
// 2. 手动触发GC
System.out.println("\n【2. 手动触发GC】");
System.gc();
Thread.sleep(500);
printMemoryInfo("调用System.gc()后");
// 3. 对象晋升演示
System.out.println("\n【3. 对象晋升演示】");
objectPromotion();
// 4. 不同引用类型GC行为
System.out.println("\n【4. 引用类型GC行为】");
referenceTypeGC();
// 5. 内存泄漏模拟
System.out.println("\n【5. 内存泄漏模拟】");
memoryLeakDemo();
}
private static void printMemoryInfo(String phase) {
long total = runtime.totalMemory();
long free = runtime.freeMemory();
long used = total - free;
long max = runtime.maxMemory();
System.out.printf("%-20s: 已用=%6dMB, 空闲=%6dMB, 总量=%6dMB, 最大=%6dMB%n",
phase,
used / 1024 / 1024,
free / 1024 / 1024,
total / 1024 / 1024,
max / 1024 / 1024);
}
/**
* 对象晋升演示
*/
private static void objectPromotion() throws InterruptedException {
// 创建长期存活对象
List<byte[]> longLived = new ArrayList<>();
for (int i = 0; i < 10; i++) {
longLived.add(new byte[512 * 1024]); // 512KB
}
// 多次触发GC,让对象晋升
for (int i = 0; i < 20; i++) {
// 创建临时对象
for (int j = 0; j < 50; j++) {
byte[] temp = new byte[1024 * 1024];
}
// 触发GC
System.gc();
Thread.sleep(100);
if (i % 5 == 0) {
System.out.println("GC次数: " + (i + 1));
// 检查对象是否晋升到老年代需要JVM工具
}
}
System.out.println("长期存活对象最终晋升到老年代");
}
/**
* 不同引用类型GC行为
*/
private static void referenceTypeGC() throws InterruptedException {
// 强引用
Object strong = new Object();
// 软引用
java.lang.ref.SoftReference<Object> soft =
new java.lang.ref.SoftReference<>(new Object());
// 弱引用
java.lang.ref.WeakReference<Object> weak =
new java.lang.ref.WeakReference<>(new Object());
System.out.println("GC前:");
System.out.println(" 强引用: " + strong);
System.out.println(" 软引用: " + soft.get());
System.out.println(" 弱引用: " + weak.get());
System.gc();
Thread.sleep(100);
System.out.println("\nGC后:");
System.out.println(" 强引用: " + strong + " (仍然存在)");
System.out.println(" 软引用: " + soft.get() + " (内存足够仍存在)");
System.out.println(" 弱引用: " + weak.get() + " (已被回收)");
// 分配大量内存,触发软引用回收
System.out.println("\n分配大量内存...");
try {
List<byte[]> memory = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
memory.add(new byte[1024 * 1024]);
}
} catch (OutOfMemoryError e) {
System.out.println("OOM: " + e.getMessage());
}
System.out.println("内存紧张后:");
System.out.println(" 软引用: " + soft.get() + " (可能被回收)");
}
/**
* 内存泄漏模拟
*/
private static void memoryLeakDemo() {
ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();
threadLocal.set(new byte[10 * 1024 * 1024]); // 10MB
// 如果不remove,线程池场景下会泄漏
System.out.println("ThreadLocal设置10MB数据");
System.out.println("如果不调用remove(),线程复用时导致泄漏");
// 正确做法
threadLocal.remove();
System.out.println("已调用remove()清理");
}
}
完整示例:GC调优实践
java
import java.util.*;
import java.util.concurrent.*;
/**
* GC调优实践示例
*/
public class GCTuningDemo {
public static void main(String[] args) {
System.out.println("=== GC调优实践 ===\n");
// 1. 确定调优目标
printTuningGoals();
// 2. 分析当前GC状况
analyzeCurrentGC();
// 3. 选择合适的收集器
selectCollector();
// 4. 调整内存参数
tuneMemory();
// 5. 性能测试验证
performanceTest();
}
/**
* 确定调优目标
*/
private static void printTuningGoals() {
System.out.println("【1. 确定调优目标】\n");
System.out.println("常见调优目标:");
System.out.println(" 1. 降低GC停顿时间(响应时间优先)");
System.out.println(" 2. 提高吞吐量(吞吐量优先)");
System.out.println(" 3. 降低GC频率");
System.out.println(" 4. 避免Full GC");
System.out.println();
System.out.println("目标量化:");
System.out.println(" - GC停顿 < 100ms");
System.out.println(" - 吞吐量 > 95%");
System.out.println(" - Full GC频率 < 1次/小时");
System.out.println();
}
/**
* 分析当前GC状况
*/
private static void analyzeCurrentGC() {
System.out.println("【2. 分析当前GC状况】\n");
// 使用JMX获取GC信息
List<GarbageCollectorMXBean> gcBeans =
ManagementFactory.getGarbageCollectorMXBeans();
System.out.println("当前GC统计:");
for (GarbageCollectorMXBean gcBean : gcBeans) {
System.out.println(" " + gcBean.getName() + ":");
System.out.println(" 收集次数: " + gcBean.getCollectionCount());
System.out.println(" 收集时间: " + gcBean.getCollectionTime() + "ms");
}
System.out.println();
System.out.println("分析要点:");
System.out.println(" 1. Minor GC频率和耗时");
System.out.println(" 2. Full GC频率和耗时");
System.out.println(" 3. GC原因分析");
System.out.println(" 4. 内存分配速率");
System.out.println();
}
/**
* 选择合适的收集器
*/
private static void selectCollector() {
System.out.println("【3. 选择收集器】\n");
System.out.println("选择依据:");
System.out.println("┌──────────────────┬────────────────────┬─────────────────┐");
System.out.println("│ 场景 │ 推荐收集器 │ 参数 │");
System.out.println("├──────────────────┼────────────────────┼─────────────────┤");
System.out.println("│ 单核/小内存 │ Serial GC │ -XX:+UseSerialGC│");
System.out.println("│ 多核/吞吐优先 │ Parallel GC │ -XX:+UseParallelGC│");
System.out.println("│ 多核/延迟优先 │ G1 GC │ -XX:+UseG1GC │");
System.out.println("│ 大堆/低延迟 │ ZGC │ -XX:+UseZGC │");
System.out.println("│ 超大堆/极低延迟 │ ZGC (分代) │ -XX:+UseZGC -XX:+ZGenerational│");
System.out.println("└──────────────────┴────────────────────┴─────────────────┘");
System.out.println();
}
/**
* 调整内存参数
*/
private static void tuneMemory() {
System.out.println("【4. 内存参数调优】\n");
System.out.println("基本原则:");
System.out.println(" 1. 堆内存 = 年轻代 + 老年代");
System.out.println(" 2. 年轻代 = Eden + 2 * Survivor");
System.out.println(" 3. 新生代:老年代 ≈ 1:2 (可根据对象存活率调整)");
System.out.println();
System.out.println("常用参数:");
System.out.println(" -Xms4g 初始堆大小");
System.out.println(" -Xmx4g 最大堆大小(与Xms相同避免动态扩展)");
System.out.println(" -Xmn2g 新生代大小");
System.out.println(" -XX:NewRatio=2 新生代:老年代 = 1:2");
System.out.println(" -XX:SurvivorRatio=8 Eden:Survivor = 8:1");
System.out.println(" -XX:MaxTenuringThreshold=15 晋升年龄");
System.out.println();
System.out.println("调优策略:");
System.out.println(" 1. 增大新生代 -> 减少Minor GC频率");
System.out.println(" 2. 增大Survivor -> 延长对象在新生代时间");
System.out.println(" 3. 降低晋升年龄 -> 加快对象进入老年代");
System.out.println(" 4. 增大堆 -> 减少GC频率,但增加单次GC时间");
System.out.println();
}
/**
* 性能测试验证
*/
private static void performanceTest() {
System.out.println("【5. 性能测试验证】\n");
// 模拟业务负载
System.out.println("运行性能测试...");
long startTime = System.currentTimeMillis();
long totalGCTime = 0;
int gcCount = 0;
// 记录初始GC时间
long initialGCTime = getTotalGCTime();
// 模拟业务操作
ExecutorService executor = Executors.newFixedThreadPool(10);
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 100; i++) {
futures.add(executor.submit(() -> {
List<byte[]> list = new ArrayList<>();
for (int j = 0; j < 100; j++) {
list.add(new byte[1024]);
}
return null;
}));
}
// 等待完成
for (Future<?> future : futures) {
try {
future.get();
} catch (Exception e) {
e.printStackTrace();
}
}
executor.shutdown();
long endTime = System.currentTimeMillis();
long finalGCTime = getTotalGCTime();
// 计算指标
long totalTime = endTime - startTime;
long gcTime = finalGCTime - initialGCTime;
double throughput = 100.0 * (totalTime - gcTime) / totalTime;
System.out.println();
System.out.println("性能指标:");
System.out.println(" 总运行时间: " + totalTime + "ms");
System.out.println(" GC时间: " + gcTime + "ms");
System.out.println(" 吞吐量: " + String.format("%.2f%%", throughput));
System.out.println();
System.out.println("验证要点:");
System.out.println(" 1. 吞吐量是否达标");
System.out.println(" 2. GC停顿是否在预期范围内");
System.out.println(" 3. 无Full GC或Full GC频率很低");
System.out.println(" 4. 内存使用平稳无泄漏");
}
private static long getTotalGCTime() {
long total = 0;
for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) {
total += gcBean.getCollectionTime();
}
return total;
}
}
四、实战应用场景
场景1:高并发系统GC调优
java
/**
* 高并发系统GC调优示例
*/
public class HighConcurrencyGCTuning {
/*
场景特征:
- 请求量大
- 对象创建频繁
- 要求低延迟
- 响应时间敏感
问题:
- 频繁Minor GC
- 可能的Full GC
- GC停顿影响响应时间
调优目标:
- GC停顿 < 50ms
- 吞吐量 > 95%
- 避免Full GC
*/
public static void main(String[] args) {
System.out.println("=== 高并发系统GC调优 ===\n");
printRecommendedConfig();
printTuningSteps();
}
private static void printRecommendedConfig() {
System.out.println("【推荐配置】\n");
System.out.println("G1 GC (JDK 11+):");
System.out.println(" java -Xms8g -Xmx8g \\");
System.out.println(" -XX:+UseG1GC \\");
System.out.println(" -XX:MaxGCPauseMillis=50 \\");
System.out.println(" -XX:G1HeapRegionSize=4m \\");
System.out.println(" -XX:InitiatingHeapOccupancyPercent=40 \\");
System.out.println(" -XX:G1ReservePercent=15 \\");
System.out.println(" -XX:G1HeapWastePercent=5 \\");
System.out.println(" -XX:+ExplicitGCInvokesConcurrent \\");
System.out.println(" -jar app.jar");
System.out.println();
System.out.println("ZGC (JDK 21+):");
System.out.println(" java -Xms8g -Xmx8g \\");
System.out.println(" -XX:+UseZGC \\");
System.out.println(" -XX:+ZGenerational \\");
System.out.println(" -XX:ConcGCThreads=2 \\");
System.out.println(" -XX:ZCollectionInterval=5 \\");
System.out.println(" -jar app.jar");
System.out.println();
}
private static void printTuningSteps() {
System.out.println("【调优步骤】\n");
System.out.println("1. 监控分析");
System.out.println(" - 使用JVisualVM/JConsole监控GC");
System.out.println(" - 分析GC日志找出问题");
System.out.println();
System.out.println("2. 调整堆大小");
System.out.println(" - 增大堆减少GC频率");
System.out.println(" - 但不要超过物理内存的80%");
System.out.println();
System.out.println("3. 调整新生代比例");
System.out.println(" - 对象存活率低 -> 增大新生代");
System.out.println(" - 对象存活率高 -> 减小新生代");
System.out.println();
System.out.println("4. 优化代码");
System.out.println(" - 减少临时对象创建");
System.out.println(" - 使用对象池");
System.out.println(" - 避免大对象");
System.out.println();
System.out.println("5. 持续监控");
System.out.println(" - 部署后持续监控GC指标");
System.out.println(" - 根据实际情况微调参数");
System.out.println();
}
}
五、总结与最佳实践
核心要点回顾
| 概念 | 内容 |
|---|---|
| 存活判定 | 可达性分析算法 |
| 引用类型 | 强、软、弱、虚四种引用 |
| GC算法 | 标记-清除、标记-复制、标记-整理 |
| 收集器 | Serial、Parallel、CMS、G1、ZGC |
收集器选择指南
┌─────────────────────────────────────────────────────────────┐
│ 收集器选择决策树 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 堆内存大小? │
│ │ │
│ ├─ < 100MB ──► Serial GC │
│ │ │
│ ├─ < 4GB ──► Parallel GC (吞吐优先) │
│ │ 或 G1 GC (延迟优先) │
│ │ │
│ ├─ 4GB - 16GB ──► G1 GC │
│ │ │
│ └─ > 16GB ──► ZGC │
│ │
│ 延迟要求? │
│ │ │
│ ├─ < 10ms ──► ZGC │
│ │ │
│ ├─ 10-100ms ──► G1 GC │
│ │ │
│ └─ > 100ms ──► Parallel GC │
│ │
└─────────────────────────────────────────────────────────────┘
最佳实践
-
不要过早优化
- 先保证功能正确
- 有性能问题时再调优
- 基于数据和监控调优
-
选择合适的收集器
- JDK 8: Parallel GC(默认)或 G1 GC
- JDK 11+: G1 GC(默认)
- 大堆低延迟: ZGC
-
合理设置堆大小
bash# 一般设置为物理内存的50%-80% -Xms4g -Xmx4g -
开启GC日志
- 问题排查必备
- 生产环境必须开启
-
避免显式GC
- System.gc()通常不必要
- 使用-XX:+DisableExplicitGC禁用
相关JVM参数
bash
# GC选择
-XX:+UseSerialGC # Serial GC
-XX:+UseParallelGC # Parallel GC
-XX:+UseG1GC # G1 GC
-XX:+UseZGC # ZGC
# 内存配置
-Xms4g # 初始堆大小
-Xmx4g # 最大堆大小
-Xmn2g # 新生代大小
-XX:NewRatio=2 # 新生代:老年代
-XX:SurvivorRatio=8 # Eden:Survivor
# GC调优
-XX:MaxGCPauseMillis=200 # 最大停顿时间
-XX:GCTimeRatio=99 # 吞吐量目标
-XX:MaxTenuringThreshold=15 # 晋升年龄
# GC日志(JDK 11+)
-Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=10,filesize=10m
扩展阅读
- 《深入理解Java虚拟机》:周志明著,垃圾收集章节
- 《Java性能优化权威指南》:Oracle官方指南
- G1 GC官方文档:JEP 307
- ZGC官方文档:JEP 333, JEP 439