Skip to content

垃圾回收

GC算法

如何判断对象可以回收?

答案:

1. 引用计数法

  • 对象被引用时计数+1,引用失效时计数-1
  • 计数为0时可以回收
  • 缺点:无法解决循环引用
java
// 循环引用
objA.instance = objB;
objB.instance = objA;
// 两个对象互相引用,计数永远不为0

2. 可达性分析算法(Java使用)

  • 从GC Roots开始向下搜索
  • 不可达的对象可以回收

GC Roots包括

  • 虚拟机栈中引用的对象
  • 方法区中静态变量引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中引用的对象
  • 活跃线程

垃圾回收算法有哪些?

答案:

1. 标记-清除(Mark-Sweep)

  • 标记:标记所有需要回收的对象
  • 清除:回收被标记的对象

优点:简单 缺点:产生内存碎片

2. 标记-复制(Mark-Copy)

  • 将内存分为两块
  • 将存活对象复制到另一块
  • 清空原来的内存

优点:无内存碎片 缺点:浪费一半内存

新生代使用:Eden:Survivor0:Survivor1 = 8:1:1

3. 标记-整理(Mark-Compact)

  • 标记:标记所有存活对象
  • 整理:将存活对象移到一端
  • 清除:清理边界外的内存

优点:无内存碎片 缺点:移动对象成本高

老年代使用

4. 分代收集

  • 新生代:标记-复制
  • 老年代:标记-清除或标记-整理

垃圾收集器

常见的垃圾收集器?

答案:

新生代收集器
├── Serial(串行)
├── ParNew(并行)
└── Parallel Scavenge(并行)

老年代收集器
├── Serial Old(串行)
├── Parallel Old(并行)
└── CMS(并发标记清除)

全堆收集器
├── G1(分区收集)
└── ZGC(低延迟)

Serial收集器?

答案:

  • 单线程收集器
  • 收集时暂停所有工作线程(STW)
  • 适用于客户端模式
bash
-XX:+UseSerialGC

ParNew收集器?

答案:

  • Serial的多线程版本
  • 新生代收集器
  • 配合CMS使用
bash
-XX:+UseParNewGC

Parallel Scavenge收集器?

答案:

  • 多线程收集器
  • 关注吞吐量(运行用户代码时间 / 总时间)
  • 适用于后台计算
bash
-XX:+UseParallelGC
-XX:MaxGCPauseMillis=100  # 最大停顿时间
-XX:GCTimeRatio=99        # 吞吐量

CMS收集器?

答案: Concurrent Mark Sweep,并发标记清除,关注最短停顿时间。

工作流程

1. 初始标记(STW)
   - 标记GC Roots直接关联的对象

2. 并发标记
   - 从GC Roots遍历对象图

3. 重新标记(STW)
   - 修正并发标记期间变动的对象

4. 并发清除
   - 清除垃圾对象

优点

  • 并发收集,停顿时间短

缺点

  • 占用CPU资源
  • 无法处理浮动垃圾
  • 产生内存碎片
bash
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=80  # 老年代使用80%时触发

G1收集器?

答案: Garbage First,面向服务端的收集器,JDK9默认。

特点

  • 分区收集(Region)
  • 可预测的停顿时间
  • 整体标记-整理,局部标记-复制

Region

  • 将堆划分为多个大小相等的Region
  • 每个Region可以是Eden、Survivor、Old、Humongous

工作流程

1. 初始标记(STW)
2. 并发标记
3. 最终标记(STW)
4. 筛选回收(STW)
   - 根据停顿时间目标,选择回收价值最大的Region
bash
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200  # 期望停顿时间
-XX:G1HeapRegionSize=1m   # Region大小

ZGC收集器?

答案: JDK11引入的低延迟收集器。

特点

  • 停顿时间不超过10ms
  • 支持TB级堆
  • 使用染色指针和读屏障
bash
-XX:+UseZGC

GC类型

Minor GC、Major GC、Full GC的区别?

答案:

Minor GC(Young GC)

  • 新生代GC
  • 频繁触发,速度快
  • Eden区满时触发

Major GC

  • 老年代GC
  • 通常伴随至少一次Minor GC

Full GC

  • 整堆GC(新生代+老年代+方法区)
  • 速度慢,尽量避免

触发Full GC的条件

  • 老年代空间不足
  • 方法区空间不足
  • System.gc()
  • CMS GC失败

GC日志

如何查看GC日志?

答案:

bash
# JDK8
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:gc.log

# JDK9+
-Xlog:gc*:file=gc.log:time,level,tags

日志示例

[GC (Allocation Failure) [PSYoungGen: 2048K->512K(2560K)] 2048K->720K(9728K), 0.0012345 secs]

解释:
- GC类型:Minor GC
- 原因:分配失败
- 收集器:PSYoungGen(Parallel Scavenge)
- 新生代:2048K->512K(回收前->回收后)
- 新生代总大小:2560K
- 整堆:2048K->720K
- 整堆总大小:9728K
- 耗时:0.0012345秒

练习题

  1. 什么时候会触发Full GC?如何避免?
  2. CMS和G1的区别?
  3. 如何选择垃圾收集器?
  4. 如何分析GC日志?

Released under the MIT License.