08-Java语言核心-JVM原理-垃圾收集详解

垃圾收集详解

一、知识概述

垃圾收集(Garbage Collection,GC)是Java自动内存管理的核心机制。JVM会自动回收不再使用的对象占用的内存,开发者无需手动释放内存。理解垃圾收集原理对于性能优化和问题排查至关重要。

垃圾收集要解决的问题

  1. 哪些内存需要回收? - 判断对象是否存活
  2. 什么时候回收? - GC触发时机
  3. 如何回收? - 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                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

最佳实践

  1. 不要过早优化

    • 先保证功能正确
    • 有性能问题时再调优
    • 基于数据和监控调优
  2. 选择合适的收集器

    • JDK 8: Parallel GC(默认)或 G1 GC
    • JDK 11+: G1 GC(默认)
    • 大堆低延迟: ZGC
  3. 合理设置堆大小

    bash 复制代码
    # 一般设置为物理内存的50%-80%
    -Xms4g -Xmx4g
  4. 开启GC日志

    • 问题排查必备
    • 生产环境必须开启
  5. 避免显式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
相关推荐
逸Y 仙X2 小时前
文章十四:ElasticSearch Reindex重建索引
java·大数据·数据库·elasticsearch·搜索引擎·全文检索
wregjru2 小时前
【读书笔记】Effective C++ 条款8:别让异常逃离析构函数
java·开发语言
harder3212 小时前
Swift 面向协议编程的 RMP 模式
开发语言·ios·mvc·swift·策略模式
烤麻辣烫2 小时前
I/O流 进阶流
java·开发语言·学习·intellij-idea
艾莉丝努力练剑2 小时前
【QT】QT快捷键整理
linux·运维·服务器·开发语言·图像处理·人工智能·qt
程序员_大白2 小时前
【2025版】最新Qt下载安装及配置教程(非常详细)零基础入门到精通,收藏这篇就够了
开发语言·qt
枫叶丹42 小时前
【HarmonyOS 6.0】ArkData 分布式数据对象新特性:资产传输进度监听与接续传输能力深度解析
开发语言·分布式·华为·wpf·harmonyos
冷血~多好2 小时前
mysql实现主从复制以及springboot实现读写分离
java·数据库·mysql·springboot
高亚奇2 小时前
QT版本 MSVC/MinGW/GCC 含义及如何区分
开发语言·qt