【后端】【JAVA】JDK 21与JDK 7 JVM结构及GC算法深度解析:从永久代到元空间,从CMS到ZGC的演进

📖目录

  • 引言:JVM的"身体"与"心脏"
  • [1. JDK 7的JVM结构及GC算法:永久代的烦恼](#1. JDK 7的JVM结构及GC算法:永久代的烦恼)
    • [1.1 JDK 7的JVM身体结构](#1.1 JDK 7的JVM身体结构)
    • [1.2 JDK 7的GC算法:老式发动机](#1.2 JDK 7的GC算法:老式发动机)
  • [2. JDK 21的JVM结构及GC算法:元空间的革命](#2. JDK 21的JVM结构及GC算法:元空间的革命)
    • [2.1 JDK 21的JVM身体结构](#2.1 JDK 21的JVM身体结构)
    • [2.2 JDK 21的GC算法:现代低延迟发动机](#2.2 JDK 21的GC算法:现代低延迟发动机)
  • [3. JDK 7 vs JDK 21:JVM结构与GC算法对比](#3. JDK 7 vs JDK 21:JVM结构与GC算法对比)
  • [4. 现代版本推荐的GC算法:为什么选择它们?](#4. 现代版本推荐的GC算法:为什么选择它们?)
    • [4.1 G1 GC:平衡之王](#4.1 G1 GC:平衡之王)
    • [4.2 ZGC:低延迟的王者](#4.2 ZGC:低延迟的王者)
    • [4.3 Shenandoah GC:ZGC的替代品](#4.3 Shenandoah GC:ZGC的替代品)
  • [5. 源码解析:GC算法的执行流程](#5. 源码解析:GC算法的执行流程)
    • [5.1 G1 GC的核心流程](#5.1 G1 GC的核心流程)
    • [5.2 ZGC的核心流程](#5.2 ZGC的核心流程)
    • [5.3 源码解析:ZGC的着色指针实现](#5.3 源码解析:ZGC的着色指针实现)
  • 六、实战:GC算法选择与配置
    • [6.1 选择GC算法的简单指南](#6.1 选择GC算法的简单指南)
    • [6.2 实际应用中的GC配置](#6.2 实际应用中的GC配置)
  • [7. 结论:JVM的进化之路](#7. 结论:JVM的进化之路)
  • [8. 经典书籍推荐](#8. 经典书籍推荐)
  • [9. 写在最后](#9. 写在最后)

引言:JVM的"身体"与"心脏"

想象一下,你有一辆汽车,它需要定期保养才能跑得更远、更稳。JVM就是Java程序的"汽车",而GC(垃圾回收)则是它的"发动机保养系统"。JDK 7和JDK 21就像这辆汽车的两个不同版本------一个用的是老式发动机,一个用的是最新科技。

JVM的内存结构是它的"身体",而GC算法是它的"心脏",负责清理"垃圾"(不再使用的对象)。随着Java的发展,JVM的身体结构和心脏功能都在不断进化,让Java应用更高效、更稳定。

1. JDK 7的JVM结构及GC算法:永久代的烦恼

1.1 JDK 7的JVM身体结构

在JDK 7中,JVM的内存结构就像一个老式仓库,分为几个固定区域:

复制代码
┌───────────────────────────────┐
│          JVM内存结构          │
├─────────────┬───────────────┤
│  方法区(PermGen)  │    堆内存       │
│             │  ┌───────────┐  │
│             │  │ 新生代    │  │
│             │  │  ├───────┤  │
│             │  │  │ Eden  │  │
│             │  │  ├───────┤  │
│             │  │  │ S0/S1 │  │
│             │  └───────────┘  │
│             │  ┌───────────┐  │
│             │  │ 老年代    │  │
│             │  └───────────┘  │
├─────────────┴───────────────┤
│  栈、PC寄存器、本地方法栈    │
└───────────────────────────────┘

永久代(PermGen) :这是JDK 7的"老大难"问题。它就像一个固定大小的仓库,用来存放类的元数据(类名、方法、常量等)。问题在于,这个仓库的大小是固定的,如果应用加载的类太多,就会出现OutOfMemoryError: PermGen space错误。

堆内存:分为新生代(Eden和Survivor区)和老年代。新生代是"新生儿",老年代是"老年人"。

1.2 JDK 7的GC算法:老式发动机

JDK 7主要使用以下GC算法:

  1. Serial GC:单线程回收,像一个人在打扫房间,简单但效率低
  2. Parallel GC:多线程回收,像多个人一起打扫,效率高但暂停时间长
  3. CMS GC:并发标记清除,像一边打扫一边继续工作,暂停时间短,但容易"内存碎片"

CMS GC的痛点:它在标记阶段会暂停所有应用线程(STW),虽然比其他GC暂停时间短,但仍然不够理想。而且,CMS在处理大堆内存时,容易出现"并发模式失败"。

2. JDK 21的JVM结构及GC算法:元空间的革命

2.1 JDK 21的JVM身体结构

JDK 21彻底改变了JVM的内存结构,最显著的变化是永久代(PermGen)被元空间(Metaspace)取代

复制代码
┌───────────────────────────────┐
│          JVM内存结构          │
├─────────────┬───────────────┤
│  元空间(Metaspace)  │    堆内存       │
│             │  ┌───────────┐  │
│             │  │ 新生代    │  │
│             │  │  ├───────┤  │
│             │  │  │ Eden  │  │
│             │  │  ├───────┤  │
│             │  │  │ S0/S1 │  │
│             │  └───────────┘  │
│             │  ┌───────────┐  │
│             │  │ 老年代    │  │
│             │  └───────────┘  │
├─────────────┴───────────────┤
│  栈、PC寄存器、本地方法栈    │
└───────────────────────────────┘

元空间(Metaspace) :这是JDK 8引入的革命性变化,它使用本地内存(Native Memory)来存储类的元数据,不再受JVM堆内存的限制。就像把仓库从固定大小的房间搬到了可以无限扩展的仓库,再也不用担心"仓库满了"的问题。

堆内存:与JDK 7相比,堆内存结构基本保持不变,但GC算法有了质的飞跃。

2.2 JDK 21的GC算法:现代低延迟发动机

JDK 21中,GC算法有了重大升级:

  1. G1 GC:默认GC算法,JDK 9开始,是JDK 7中Parallel GC的升级版
  2. ZGC:低延迟GC,JDK 11开始,暂停时间通常在10ms以下
  3. Shenandoah GC:低延迟GC,JDK 12开始,与ZGC类似,但实现方式不同

ZGC的"黑科技":ZGC使用了"着色指针"(Colored Pointers)和"并发重映射"(Concurrent Remapping)技术,可以在应用线程运行的同时进行垃圾回收,实现几乎"零暂停"。

Shenandoah GC:它使用"Brooks Pointer"技术,通过提前准备指针,实现并发回收,暂停时间也控制在10ms以下。

3. JDK 7 vs JDK 21:JVM结构与GC算法对比

特性 JDK 7 JDK 21 说明
内存结构 永久代(PermGen) 元空间(Metaspace) 永久代被移除,元空间使用本地内存
默认GC Parallel GC G1 GC JDK 21默认使用G1 GC
低延迟GC ZGC, Shenandoah GC JDK 21提供真正的低延迟GC
永久代问题 频繁出现PermGen space错误 不存在 元空间自动扩展,不再受限
GC暂停时间 CMS: 100-500ms ZGC: <10ms ZGC实现了真正的低延迟
GC算法数量 3种 5种+ JDK 21提供更多选择
JDK 8+支持 永久代在JDK 8中开始被移除

关键变化总结

  • 永久代(PermGen)被元空间(Metaspace)取代,解决了"类加载器内存溢出"问题
  • CMS GC在JDK 14中被移除,G1 GC成为默认GC
  • ZGC和Shenandoah GC成为低延迟应用的首选

4. 现代版本推荐的GC算法:为什么选择它们?

4.1 G1 GC:平衡之王

为什么推荐:G1 GC在吞吐量和延迟之间取得了良好的平衡,特别适合大多数中大型应用。它的设计目标是"可预测的停顿时间",而不是"最大吞吐量"。

大白话解释:想象一下,你正在超市购物,G1 GC就像一个高效的收银员,它会提前计算好你需要排队的时间,让你知道大概要等多久,而不是让你在收银台前干等。

适用场景

  • 中大型Java应用
  • 对延迟有一定要求,但不需要极致低延迟
  • 堆内存在4GB到32GB之间的应用

4.2 ZGC:低延迟的王者

为什么推荐:ZGC的暂停时间可以控制在10ms以下,甚至可以达到1ms,这对于实时交易系统、高频交易、在线游戏等场景至关重要。

大白话解释:ZGC就像一个超级高效的快递员,他可以在你购物的同时,把包裹准确地送到你手中,几乎不会让你等待。

适用场景

  • 低延迟要求极高的应用(如高频交易系统)
  • 实时系统(如在线游戏、实时通信)
  • 堆内存较大的应用(>4GB)

4.3 Shenandoah GC:ZGC的替代品

为什么推荐:Shenandoah GC与ZGC类似,但实现方式不同。它在某些场景下可能比ZGC表现更好,特别是在多CPU系统上。

大白话解释:Shenandoah GC就像ZGC的"双胞胎兄弟",他们都能做到低延迟,但使用了不同的技术。

适用场景

  • 与ZGC类似,但可能在某些硬件配置上表现更好
  • 需要与ZGC对比测试的场景

5. 源码解析:GC算法的执行流程

5.1 G1 GC的核心流程

G1 GC的执行流程可以简化为以下几个步骤:

复制代码
┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  初始标记   │───▶│  并发标记   │───▶│  重新标记   │───▶│  清理阶段   │
└─────────────┘    └─────────────┘    └─────────────┘    └─────────────┘
     (STW)              (并发)              (STW)              (并发)

关键点

  • 初始标记:暂停所有应用线程(STW),标记根对象
  • 并发标记:应用线程继续运行,GC线程标记可达对象
  • 重新标记:暂停所有应用线程(STW),修正并发标记期间的变化
  • 清理阶段:应用线程继续运行,GC线程清理无用对象

5.2 ZGC的核心流程

ZGC的执行流程更加复杂,但核心思想是"并发":

复制代码
┌─────────────────────────────────────────────────────────────────────┐
│  ZGC的"着色指针"与"并发重映射"技术                                  │
├─────────────────┬─────────────────┬─────────────────┬─────────────────┤
│  1. 读取对象    │  2. 重映射指针  │  3. 重映射完成  │  4. 清理垃圾    │
│  (应用线程)     │  (GC线程)       │  (GC线程)       │  (GC线程)       │
└─────────────────┴─────────────────┴─────────────────┴─────────────────┘

关键点

  • 着色指针:ZGC使用指针的某些位来标记对象状态(如"正在被GC")
  • 并发重映射:GC线程在应用线程运行的同时,逐步重映射对象指针
  • 几乎零停顿:应用线程在GC过程中几乎不会被暂停

5.3 源码解析:ZGC的着色指针实现

ZGC的着色指针实现是其核心创新之一,以下是简化版的代码逻辑:

java 复制代码
// ZGC中着色指针的实现(简化版)
public class ZCObject {
    // 指针的高4位用于着色
    private long pointer;
    
    // 着色状态
    private enum Color {
        UNCOLORED,  // 未着色
        MARKED,     // 已标记
        REMAPPED,   // 已重映射
        COLORED     // 已着色
    }
    
    // 获取对象的着色状态
    public Color getColor() {
        // 提取指针的高4位
        int colorBits = (int) ((pointer >> 60) & 0xF);
        return Color.values()[colorBits];
    }
    
    // 设置对象的着色状态
    public void setColor(Color color) {
        // 清除高4位
        long newPointer = pointer & 0x0FFFFFFFFFFFFFFF;
        // 设置高4位
        newPointer |= ((long) color.ordinal() << 60);
        pointer = newPointer;
    }
}

大白话解释:ZGC就像在每件物品上贴了一个小标签(着色指针),这个标签有4位,可以表示16种状态。当GC需要处理某个对象时,它会查看这个标签,决定如何处理。应用线程在处理物品时,会看到标签,知道这个物品是否正在被GC处理。

六、实战:GC算法选择与配置

6.1 选择GC算法的简单指南

java 复制代码
// JDK 21中配置GC算法的示例
public class GcConfigExample {
    public static void main(String[] args) {
        // G1 GC(默认)
        System.out.println("G1 GC (default): -XX:+UseG1GC");
        
        // ZGC
        System.out.println("ZGC: -XX:+UseZGC");
        
        // Shenandoah GC
        System.out.println("Shenandoah GC: -XX:+UseShenandoahGC");
        
        // 设置最大暂停时间(ZGC示例)
        System.out.println("ZGC with max pause time: -XX:MaxGCPauseMillis=10");
    }
}

6.2 实际应用中的GC配置

java 复制代码
// 生产环境推荐的GC配置示例
public class ProductionGcConfig {
    public static void main(String[] args) {
        // 对于大多数应用,G1 GC是好的选择
        // 4GB堆内存,目标暂停时间100ms
        System.out.println("G1 GC with 4GB heap, max pause 100ms:");
        System.out.println("java -Xms4g -Xmx4g -XX:+UseG1GC -XX:MaxGCPauseMillis=100");
        
        // 对于低延迟应用,ZGC是更好的选择
        System.out.println("ZGC with 8GB heap, max pause 10ms:");
        System.out.println("java -Xms8g -Xmx8g -XX:+UseZGC -XX:MaxGCPauseMillis=10");
    }
}

7. 结论:JVM的进化之路

从JDK 7到JDK 21,JVM的进化不仅仅是技术的升级,更是对Java应用性能和稳定性的重大提升。元空间的引入 解决了永久代的内存限制问题,ZGC和Shenandoah GC的出现则为低延迟应用提供了强大支持。

为什么这些变化重要

  • 永久代→元空间:避免了类加载器内存溢出,让应用更稳定
  • CMS→G1:G1在吞吐量和延迟之间取得了更好的平衡
  • G1→ZGC/Shenandoah:实现了真正的低延迟,满足了现代应用的需求

8. 经典书籍推荐

  1. 《Java Performance》 by Scott Oaks (最新版)

    • 2024年最新版,详细讲解了JVM性能调优、GC算法和内存管理
    • 书中包含大量实战案例,特别适合生产环境调优
    • 书中有一章专门讲解ZGC和Shenandoah GC的实践
  2. 《The Garbage Collection Handbook》 by Richard Jones, Antony Hosking, Eliot Moss

    • GC领域的"圣经",详细讲解了各种GC算法的原理和实现
    • 虽然出版较早,但内容依然权威,是理解GC的必读之作
  3. 《Java Virtual Machine Specification》 (Java SE 21)

    • Java官方文档,详细描述了JVM的规范和实现
    • 适合深入研究JVM内部机制

9. 写在最后

JDK 7到JDK 21的JVM进化,就像从老式自行车升级到现代电动车。虽然自行车也能骑,但电动车更舒适、更高效、更可靠。同样,JDK 21的JVM结构和GC算法,为Java应用提供了更强大、更稳定的运行环境。

作为Java开发者,了解JVM的演进和GC算法,不仅有助于解决性能问题,更能让我们写出更高质量的代码。就像了解汽车的发动机原理,能让我们更好地驾驶和维护车辆一样。

最后提醒:不要盲目追求"最新"GC算法,而是根据应用需求选择最适合的。对于大多数应用,G1 GC已经足够;对于低延迟要求高的应用,ZGC或Shenandoah GC是更好的选择。


参考链接

本文为原创,遵循 CC 4.0 BY-SA 协议。转载请附原文链接及本声明。

相关推荐
Maỿbe2 小时前
JVM中的内存结构
jvm
寻星探路2 小时前
网络原理全景图:从通信起源到 TCP/IP 体系架构深度拆解
java·网络·c++·python·tcp/ip·http·架构
未来之窗软件服务2 小时前
幽冥大陆(八十八 ) 操作系统应用封装技术C#自解压 —东方仙盟练气期
java·前端·c#·软件打包·仙盟创梦ide·东方仙盟·阿雪技术观
技术小泽2 小时前
java转go速成入门笔记篇(一)
java·笔记·golang
你不是我我2 小时前
【Java 开发日记】我们来说一下 MySQL 的慢查询日志
android·java·mysql
C雨后彩虹2 小时前
ReentrantLock入门:核心特性与基本使用
java·数据结构·reentrantlock·lock
资生算法程序员_畅想家_剑魔2 小时前
Java常见技术分享-27-事务安全-事务日志-事务日志框架
java·开发语言
古城小栈2 小时前
内存对决:rust、go、java、python、nodejs
java·golang·rust
予枫的编程笔记2 小时前
【Java 进阶3】Kafka从入门到实战:全面解析分布式消息队列的核心与应用
java·分布式·kafka