垃圾回收

垃圾回收(Garbage Collection, GC)是JVM自动管理内存的机制,负责回收不再使用的对象占用的内存空间。Java开发者不需要手动释放内存,这大大减少了内存泄漏和指针错误的风险。


为什么需要垃圾回收

  • 防止内存泄漏
  • 避免手动内存管理的复杂性
  • 提高开发效率
  • 保证程序稳定性

垃圾回收的基本原理

对象存活判断

引用计数法(Java未采用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Object {
int refCount = 0;

void addReference() {
refCount++;
}

void removeReference() {
refCount--;
if (refCount == 0) {
// 可以被回收
}
}
}
可达性分析算法(Java采用)

从GC Roots对象开始,向下搜索引用链,不在引用链上的对象被视为可回收。

GC Roots包括:

  • 虚拟机栈中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI引用的对象

引用类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.lang.ref.*;
public class ReferenceTypes {
public static void main(String[] args) {
// 强引用 - 不会被回收
Object strongRef = new Object();

// 软引用 - 内存不足时回收
SoftReference<Object> softRef = new SoftReference<>(new Object());

// 弱引用 - GC时回收
WeakReference<Object> weakRef = new WeakReference<>(new Object());

// 虚引用 - 主要用于跟踪对象被回收的状态
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);

System.out.println("Strong: " + strongRef);
System.out.println("Soft: " + softRef.get());
System.out.println("Weak: " + weakRef.get());
System.out.println("Phantom: " + phantomRef.get());

// 触发GC
System.gc();
System.out.println("After GC:");
System.out.println("Strong: " + strongRef);
System.out.println("Soft: " + softRef.get());
System.out.println("Weak: " + weakRef.get());
System.out.println("Phantom: " + phantomRef.get());
}
}

垃圾回收算法

标记-清除算法

缺点:产生内存碎片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void markAndSweep() {
// 标记阶段
for (Object obj : heap) {
if (isReachable(obj)) {
obj.marked = true;
}
}

// 清除阶段
for (Object obj : heap) {
if (!obj.marked) {
free(obj);
}
}
}

复制算法

将内存分为两块,只使用其中一块。GC时将存活对象复制到另一块,然后清除当前块。

优点:无内存碎片

缺点:内存利用率低

1
2
3
4
5
6
7
8
9
void copy() {
for (Object obj : fromSpace) {
if (isReachable(obj)) {
copyTo(obj, toSpace);
}
}
swap(fromSpace, toSpace);
clear(toSpace);
}

标记-整理算法

优点:无内存碎片,内存利用率高

缺点:移动对象成本高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void markAndCompact() {
// 标记阶段
for (Object obj : heap) {
if (isReachable(obj)) {
obj.marked = true;
}
}

// 整理阶段
int newAddr = 0;
for (Object obj : heap) {
if (obj.marked) {
move(obj, newAddr);
newAddr += obj.size;
}
}

// 清理剩余空间
freeMemoryFrom(newAddr);
}

JVM内存分代与GC类型

堆内存结构

1
2
3
4
5
6
Young Generation (1/3 heap)
├─ Eden (80%)
├─ Survivor 0 (10%)
└─ Survivor 1 (10%)
Old Generation (2/3 heap)
PermGen/Metaspace (非堆)

GC类型

  1. Minor GC/Young GC:只收集年轻代
  2. Major GC/Old GC:只收集老年代
  3. Full GC:收集整个堆(年轻代+老年代+方法区)

对象分配与晋升流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ObjectLifecycle {
public static void main(String[] args) {
// 对象首先在Eden区分配
Object obj1 = new Object(); // Eden

// 触发Minor GC
for (int i = 0; i < 100000; i++) {
new Object(); // 快速创建对象填满Eden
}

// 长期存活的对象会晋升到老年代
Object longLivedObj = new Object();
for (int i = 0; i < 15; i++) {
System.gc(); // 模拟多次GC使对象年龄增加
}
}
}

垃圾收集器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
串行收集器
-XX:+UseSerialGC

并行收集器(吞吐量优先)
-XX:+UseParallelGC
-XX:+UseParallelOldGC

CMS收集器(低延迟)
-XX:+UseConcMarkSweepGC

G1收集器(平衡型)
-XX:+UseG1GC

ZGC和Shenandoah(超低延迟)
-XX:+UseZGC (Java 11+)
-XX:+UseShenandoahGC

GC调优示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class GCParameters {
public static void main(String[] args) {
// 打印GC详细信息
// 运行时添加JVM参数: -XX:+PrintGCDetails
System.out.println("GC Demo");

// 创建大量对象触发GC
for (int i = 0; i < 100000; i++) {
new Object();
}

System.gc(); // 建议执行GC
}
}

设置GC参数示例

1
2
3
4
# 使用G1收集器,设置最大堆内存和初始堆内存
java -XX:+UseG1GC -Xms512m -Xmx512m -XX:+PrintGCDetails GCParameters
# 使用ParallelGC并设置吞吐量目标
java -XX:+UseParallelGC -XX:+UseParallelOldGC -XX:GCTimeRatio=99 -XX:+PrintGCDetails GCParameters

常见GC问题与解决方案

内存泄漏

解决方案:及时清除不再需要的引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.ArrayList;
import java.util.List;
public class MemoryLeak {
static List<Object> list = new ArrayList<>();

public static void main(String[] args) {
while (true) {
list.add(new byte[1024 * 1024]); // 1MB
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

频繁Full GC问题

可能原因:

  • 老年代空间不足
  • System.gc()调用
  • 大对象直接进入老年代

解决方案:

  • 增加堆大小
  • 调整新生代与老年代比例
  • 避免手动调用System.gc()

现代GC发展趋势

  1. 低延迟GC:ZGC和Shenandoah的目标是控制在10ms以内的停顿时间
  2. 云原生GC:适应容器环境的内存弹性
  3. AI驱动的GC:根据应用特点自动优化GC参数

Java的垃圾回收机制是一个复杂但设计精妙的系统,理解其工作原理对于编写高性能Java应用程序至关重要。不同的应用场景可能需要不同的GC策略和调优方法。建议在实际开发中结合监控工具(如VisualVM、JConsole、GC日志等)来分析应用的内存使用情况,并据此进行调优。