Java 垃圾回收简介
C/C++ 需要手动管理内存,忘记释放导致内存泄漏,过早释放导致野指针。Java 通过垃圾回收(Garbage Collection,GC)自动管理内存,解放了程序员的双手。但 GC 不是银弹,理解其原理才能避免内存泄漏和 GC 调优问题。
为什么需要垃圾回收
C++ 需要手动 new/delete,忘记释放导致内存泄漏,过早释放导致野指针。
Java 自动回收:对象超出作用域后不再可达,等待 GC 回收。程序员无需关心内存释放,专注于业务逻辑。
垃圾回收算法
引用计数法
每个对象有一个引用计数器,引用为 0 时回收。优点是简单、回收及时;缺点是无法处理循环引用。
可达性分析算法(JVM 使用)
从 GC Roots 开始,通过引用链判断对象是否可达。GC Roots 包括:虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中 JNI 引用的对象。
内存分区
┌─────────────────────────────────────────────────────────────┐
│ 方法区(Method Area) │ 存储类信息、常量、静态变量 │
├─────────────────────────────────────────────────────────────┤
│ 堆(Heap) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Eden 区 │ │ Survivor │ │ Survivor │ │ Old Gen │ │
│ │ (Eden) │ │ Space0 │ │ (To) │ │(Tenured)│ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 虚拟机栈(VM Stack) │ 方法调用栈帧 │
├─────────────────────────────────────────────────────────────┤
│ 程序计数器(PC) │ 字节码行号指示器 │
└─────────────────────────────────────────────────────────────┘GC 主要关注堆内存,分代回收:年轻代(大部分对象朝生夕死,GC 频率高)和老年代(存活时间长的对象,GC 频率低)。
垃圾回收器
| 回收器 | 算法 | 特点 | 适用场景 |
|---|---|---|---|
| Serial | 串行 | 简单高效 | 单线程、小数据量 |
| Parallel | 并行 | 吞吐量优先 | 科学计算、大数据 |
| CMS | 并发 | 停顿时间短 | Web 应用 |
| G1 | 分区 | 可控停顿时间 | JDK 9+ 默认 |
| ZGC | 并发 | 极低停顿(<1ms) | 大内存、低延迟 |
当前推荐:JDK 8 用 Parallel GC,JDK 9+ 用 G1 GC,大内存低延迟用 ZGC 或 Shenandoah。
GC 类型
| 类型 | 说明 | 触发条件 |
|---|---|---|
| Minor GC(Young GC) | 清理年轻代 | Eden 区满 |
| Major GC | 清理老年代 | 老年代满 |
| Full GC | 清理整个堆 | 多种条件 |
常见问题
内存泄漏
对象不再使用但无法被回收。例如静态集合持有对象引用:
java
static List<Object> list = new ArrayList<>();
public void add(Object obj) {
list.add(obj); // 对象一直被引用,无法回收
}解决:避免静态集合持有对象引用、及时清理资源、使用弱引用/软引用。
OutOfMemoryError
java
while (true) {
new Object(); // 无限创建对象导致 OOM
}解决:设置合理的堆大小 -Xms512m -Xmx512m、检查内存泄漏、优化对象创建。
GC 参数调优
bash
# 设置堆大小
java -Xms512m -Xmx512m MyApp
# 选择 GC 回收器
java -XX:+UseG1GC MyApp # 使用 G1 回收器
java -XX:+UseZGC MyApp # 使用 ZGC
java -XX:+UseShenandoahGC MyApp # 使用 Shenandoah
# 设置停顿时间目标(G1)
java -XX:MaxGCPauseMillis=200 MyApp