垃圾回收(Garbage Collection, GC)是Java虚拟机(JVM)的核心特性之一,它自动管理内存,避免了手动内存管理的复杂性和潜在风险。深入理解GC的核心算法、底层逻辑以及如何根据业务场景进行架构选型,是每一位Java开发者必备的技能。本文将用通俗的语言讲透GC算法的底层原理,结合实战示例帮助你夯实基础并解决实际问题。
1. 核心算法基础
GC的核心目标是识别并回收堆内存中不再被引用的对象,释放内存空间。现代GC算法基于两个关键假说:弱分代假说(大多数对象朝生夕死)和强分代假说(熬过多次GC的对象难以死亡)。基于这两个假说,GC算法不断演进,下面我们逐一讲解最核心的三种算法。
1.1 标记-清除算法(Mark-Sweep)
标记-清除是最基础的GC算法,分为两个阶段:标记阶段和清除阶段。原理:
- 标记阶段:从根节点(GC Roots)开始枚举,标记所有存活的对象。
- 清除阶段:遍历堆内存,回收所有未被标记的对象。
优点:实现简单,不需要额外的内存空间。缺点:
- 产生内存碎片,导致大对象无法分配。
- 标记和清除阶段的效率较低,尤其是在存活对象较多时。适用场景:适用于老年代(存活对象多,复制成本高),但现代GC很少直接使用,通常作为基础算法结合其他优化使用。
1.2 复制算法(Copying)
复制算法为了解决标记-清除的内存碎片问题而设计。原理:
- 将内存分为两块大小相等的区域(From区和To区)。
- 只使用From区,当From区满时,将存活对象复制到To区。
- 清空From区,交换From区和To区的角色。
优点:
- 没有内存碎片,分配内存时只需移动指针,效率高。
- 复制过程中只需遍历存活对象,在存活对象少的场景下效率极高。缺点:
- 需要额外的内存空间,内存利用率只有50%。
- 当存活对象较多时,复制成本较高。适用场景:年轻代(大多数对象朝生夕死,存活对象少)。现代JVM的年轻代通常将内存分为Eden区和两个Survivor区(From和To),比例通常为8:1:1,这样内存利用率可以达到90%。
1.3 标记-整理算法(Mark-Compact)
标记-整理算法结合了标记-清除和复制算法的优点,适用于老年代。原理:
- 标记阶段:与标记-清除算法相同,标记所有存活对象。
- 整理阶段:将所有存活对象向内存的一端移动,然后清除边界外的内存。
优点:
- 没有内存碎片,适合大对象分配。
- 不需要额外的内存空间,内存利用率高。缺点:
- 整理阶段需要移动对象,效率较低。
- 实现复杂度较高。适用场景:老年代(存活对象多,复制成本高)。
2. 分代收集理论
基于弱分代假说和强分代假说,现代JVM将堆内存分为年轻代(Young Generation)和老年代(Old Generation),不同代采用不同的GC算法,以提高效率。
2.1 年轻代
年轻代用于存放新创建的对象,大多数对象朝生夕死,因此采用复制算法。年轻代分为Eden区和两个Survivor区(From和To),默认比例为8:1:1。工作流程:
- 新对象优先在Eden区分配。
- 当Eden区满时,触发Minor GC(年轻代GC),将Eden区和From区的存活对象复制到To区。
- 清空Eden区和From区,交换From区和To区的角色。
- 当对象在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基础,解决实际问题。