垃圾回收核心算法:从底层逻辑到生产环境架构选型指南

垃圾回收(Garbage Collection, GC)是Java虚拟机(JVM)的核心特性之一,它自动管理内存,避免了手动内存管理的复杂性和潜在风险。深入理解GC的核心算法、底层逻辑以及如何根据业务场景进行架构选型,是每一位Java开发者必备的技能。本文将用通俗的语言讲透GC算法的底层原理,结合实战示例帮助你夯实基础并解决实际问题。

1. 核心算法基础

GC的核心目标是识别并回收堆内存中不再被引用的对象,释放内存空间。现代GC算法基于两个关键假说:弱分代假说 (大多数对象朝生夕死)和强分代假说(熬过多次GC的对象难以死亡)。基于这两个假说,GC算法不断演进,下面我们逐一讲解最核心的三种算法。

1.1 标记-清除算法(Mark-Sweep)

标记-清除是最基础的GC算法,分为两个阶段:标记阶段清除阶段原理

  1. 标记阶段:从根节点(GC Roots)开始枚举,标记所有存活的对象。

  2. 清除阶段:遍历堆内存,回收所有未被标记的对象。

优点 :实现简单,不需要额外的内存空间。 缺点

  • 产生内存碎片,导致大对象无法分配。

  • 标记和清除阶段的效率较低,尤其是在存活对象较多时。 适用场景:适用于老年代(存活对象多,复制成本高),但现代GC很少直接使用,通常作为基础算法结合其他优化使用。

1.2 复制算法(Copying)

复制算法为了解决标记-清除的内存碎片问题而设计。 原理

  1. 将内存分为两块大小相等的区域(From区和To区)。

  2. 只使用From区,当From区满时,将存活对象复制到To区。

  3. 清空From区,交换From区和To区的角色。

优点

  • 没有内存碎片,分配内存时只需移动指针,效率高。

  • 复制过程中只需遍历存活对象,在存活对象少的场景下效率极高。 缺点

  • 需要额外的内存空间,内存利用率只有50%。

  • 当存活对象较多时,复制成本较高。 适用场景:年轻代(大多数对象朝生夕死,存活对象少)。现代JVM的年轻代通常将内存分为Eden区和两个Survivor区(From和To),比例通常为8:1:1,这样内存利用率可以达到90%。

1.3 标记-整理算法(Mark-Compact)

标记-整理算法结合了标记-清除和复制算法的优点,适用于老年代。 原理

  1. 标记阶段:与标记-清除算法相同,标记所有存活对象。

  2. 整理阶段:将所有存活对象向内存的一端移动,然后清除边界外的内存。

优点

  • 没有内存碎片,适合大对象分配。

  • 不需要额外的内存空间,内存利用率高。 缺点

  • 整理阶段需要移动对象,效率较低。

  • 实现复杂度较高。 适用场景:老年代(存活对象多,复制成本高)。

2. 分代收集理论

基于弱分代假说和强分代假说,现代JVM将堆内存分为年轻代 (Young Generation)和老年代(Old Generation),不同代采用不同的GC算法,以提高效率。

2.1 年轻代

年轻代用于存放新创建的对象,大多数对象朝生夕死,因此采用复制算法 。年轻代分为Eden区和两个Survivor区(From和To),默认比例为8:1:1。 工作流程

  1. 新对象优先在Eden区分配。

  2. 当Eden区满时,触发Minor GC(年轻代GC),将Eden区和From区的存活对象复制到To区。

  3. 清空Eden区和From区,交换From区和To区的角色。

  4. 当对象在Survivor区熬过一定次数(默认15次)的Minor GC后,会被晋升到老年代。

2.2 老年代

老年代用于存放长期存活的对象,存活对象多,因此采用标记-清除标记-整理 算法。当老年代满时,触发Major GC (老年代GC)或Full GC(整个堆内存GC)。

3. 现代垃圾回收器概览

现代JVM提供了多种垃圾回收器,以满足不同的应用场景。下面我们介绍几种常用的垃圾回收器。

3.1 G1(Garbage-First)

G1是面向服务端的垃圾回收器,适用于大内存(4GB以上)场景,目标是在高吞吐量和低延迟之间取得平衡。 特点

  • 将堆内存分为多个大小相等的区域(Region),每个区域可以是Eden区、Survivor区或老年代。

  • 优先回收垃圾最多的区域(Garbage-First),提高回收效率。

  • 可预测的停顿时间,用户可以设置目标停顿时间。 适用场景 :大内存、低延迟要求的应用,如Web应用、大数据处理。 JVM参数示例

    -Xms512m -Xmx512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200

3.2 ZGC(Z Garbage Collector)

ZGC是JDK 11引入的低延迟垃圾回收器,JDK 15及以后版本正式可用。它的目标是将停顿时间控制在10ms以内,无论堆内存大小(从几百MB到几TB)。 特点

  • 基于Region的内存布局,支持动态调整Region大小。

  • 并发标记、并发整理,停顿时间极短。

  • 支持NUMA架构,提高内存访问效率。 适用场景 :对延迟要求极高的应用,如金融交易、实时系统。 JVM参数示例

    -Xms512m -Xmx512m -XX:+UseZGC

3.3 Shenandoah

Shenandoah是RedHat开发的低延迟垃圾回收器,与ZGC类似,目标是在大内存场景下实现低延迟。 特点

  • 并发整理,在整理过程中允许用户线程继续运行。

  • 支持大内存,停顿时间与堆内存大小无关。 适用场景 :大内存、低延迟要求的应用。 JVM参数示例

    -Xms512m -Xmx512m -XX:+UseShenandoahGC

4. 架构选型判断

选择合适的垃圾回收器需要考虑应用的吞吐量延迟内存大小等因素。下面是一些选型建议:

应用场景 推荐垃圾回收器 关键考虑因素
后台计算、批处理 Parallel GC 高吞吐量,对延迟要求不高
Web应用、微服务 G1 平衡吞吐量和延迟,大内存
金融交易、实时系统 ZGC/Shenandoah 极低延迟,大内存

5. 实战示例

下面我们通过一个Spring Boot应用演示如何配置和观察GC行为。

5.1 项目配置

首先创建一个Maven项目,配置pom.xml:

复制代码
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.3</version>
        <relativePath/>
    </parent>
    <groupId>com.jam.demo</groupId>
    <artifactId>gc-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gc-demo</name>
    <description>GC Demo Project</description>
    <properties>
        <java.version>17</java.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
        <fastjson2.version>2.0.43</fastjson2.version>
        <guava.version>33.0.0-jre</guava.version>
        <lombok.version>1.18.30</lombok.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>${fastjson2.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

5.2 代码实现

创建主类GcDemoApplication.java:

复制代码
package com.jam.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GcDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(GcDemoApplication.class, args);
    }
}

创建Controller类GcDemoController.java:

复制代码
package com.jam.demo.controller;

import com.jam.demo.service.GcDemoService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/api/gc")
@Tag(name = "GC演示接口", description = "用于演示垃圾回收行为的接口")
public class GcDemoController {
    @Autowired
    private GcDemoService gcDemoService;

    @PostMapping("/trigger")
    @Operation(summary = "触发GC演示", description = "创建大量对象以触发垃圾回收")
    public void triggerGc() {
        gcDemoService.create大量Objects();
    }
}

创建Service类GcDemoService.java:

复制代码
package com.jam.demo.service;

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.List;

@Slf4j
@Service
public class GcDemoService {
    private static final int OBJECT_COUNT = 100000;

    public void create大量Objects() {
        List<byte[]> tempList = Lists.newArrayListWithCapacity(OBJECT_COUNT);
        for (int i = 0; i < OBJECT_COUNT; i++) {
            tempList.add(new byte[1024]);
        }
        log.info("已创建{}个1KB的byte数组", OBJECT_COUNT);
        tempList.clear();
        System.gc();
        log.info("已触发System.gc()");
    }
}

创建配置文件application.yml:

复制代码
server:
  port: 8080

spring:
  application:
    name: gc-demo

springdoc:
  api-docs:
    path: /v3/api-docs
  swagger-ui:
    path: /swagger-ui.html

5.3 运行与观察

使用以下JVM参数运行应用:

复制代码
-Xms512m -Xmx512m -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log

启动应用后,访问Swagger UI(http://localhost:8080/swagger-ui.html),调用`/api/gc/trigger`接口,观察控制台日志和gc.log文件。可以使用GCEasy(https://gceasy.io/)工具分析gc.log文件,查看GC的次数、停顿时间、内存使用情况等。

6. 总结

本文深入讲解了垃圾回收的核心算法(标记-清除、复制、标记-整理)、分代收集理论、现代垃圾回收器(G1、ZGC、Shenandoah)以及架构选型建议,并通过实战示例演示了如何配置和观察GC行为。理解GC的底层逻辑,根据业务场景选择合适的垃圾回收器,对于优化应用性能、提高稳定性至关重要。希望本文能帮助你夯实GC基础,解决实际问题。

相关推荐
minji...2 小时前
Linux 基础IO (三) (用户缓冲区/内核缓冲区深刻理解)
java·linux·运维·服务器·c++·算法
无心水2 小时前
【常见错误】1、Java并发工具类四大坑:从ThreadLocal到ConcurrentHashMap,你踩过几个?
java·开发语言·后端·架构·threadlocal·concurrent·java并发四大坑
weixin199701080162 小时前
货铺头商品详情页前端性能优化实战
java·前端·python
惊讶的猫2 小时前
Springboot 组件注册 条件注解
java·spring boot·后端
c++之路2 小时前
Linux进程池与线程池深度解析:设计原理+实战实现(网盘项目架构)
java·linux·架构
阿里云基础软件2 小时前
当 CPU 莫名抖动时,SysOM Agent 如何 3 分钟定位元凶?
java·阿里云·智能运维·操作系统控制台·sysom
蜜獾云3 小时前
从linux内核理解Java怎样实现Socket通信
java·linux·运维
wregjru3 小时前
【读书笔记】Effective C++ 条款5~6:若不想使用编译器自动生成的函数,就该明确拒绝
java·开发语言
华科易迅3 小时前
SQL学习
java·sql·学习