设计模式与垃圾回收:策略模式选择GC算法、观察者监控GC、状态模式管理GC状态
说实话,把设计模式和垃圾回收放在一起讲,是我一直想做的事。很多同学学完设计模式,觉得就是写写业务逻辑,跟底层系统没啥关系。其实不然。我当年在优化一个高并发中间件时,就亲手用策略模式替换了GC算法,用观察者模式监控GC停顿,效果出奇的好。今天咱们就来聊聊这个组合拳。
为什么GC需要设计模式?
垃圾回收本身是个复杂系统。不同的应用场景,需要不同的GC策略。比如,批处理系统要吞吐量,游戏引擎要低延迟,Web服务器要平衡。你想想看,如果把这些策略硬编码在一起,代码会变成什么样?
我见过一个项目,GC逻辑里塞满了if-else,每次换GC算法都要改核心代码,测试一遍全量回归。那叫一个痛苦。后来我重构时,直接引入了策略模式,把GC算法抽成独立的策略类。从此,换GC就像换插件一样简单。
策略模式:让GC算法可插拔
策略模式的核心思想,就是把算法封装成独立的类,让它们可以互相替换。在GC场景下,我们可以定义统一的GC策略接口,然后实现不同的算法。
来看一个简化版的Java示例:
// GC策略接口
public interface GcStrategy {
void collect(GcContext context);
String getName();
}
// 标记-清除算法
public class MarkSweepStrategy implements GcStrategy {
@Override
public void collect(GcContext context) {
System.out.println("执行标记-清除算法...");
// 标记阶段
markPhase(context);
// 清除阶段
sweepPhase(context);
}
private void markPhase(GcContext ctx) { /* ... */ }
private void sweepPhase(GcContext ctx) { /* ... */ }
@Override
public String getName() { return "Mark-Sweep"; }
}
// 复制算法(常用于新生代)
public class CopyingStrategy implements GcStrategy {
@Override
public void collect(GcContext context) {
System.out.println("执行复制算法...");
// 将存活对象复制到另一块区域
copyLiveObjects(context);
// 清空原区域
clearFromSpace(context);
}
private void copyLiveObjects(GcContext ctx) { /* ... */ }
private void clearFromSpace(GcContext ctx) { /* ... */ }
@Override
public String getName() { return "Copying"; }
}
// 分代收集策略(组合策略)
public class GenerationalStrategy implements GcStrategy {
private GcStrategy youngStrategy = new CopyingStrategy();
private GcStrategy oldStrategy = new MarkSweepStrategy();
@Override
public void collect(GcContext context) {
if (context.isYoungGeneration()) {
youngStrategy.collect(context);
} else {
oldStrategy.collect(context);
}
}
@Override
public String getName() { return "Generational"; }
}
你看,这样设计之后,GC算法的选择就变成了配置问题。我在项目中曾经用工厂模式配合策略模式,根据JVM参数动态加载GC策略,非常灵活。
核心要点:策略模式让GC算法与调用方解耦。新增一种GC算法,只需要实现GcStrategy接口,不需要改动现有代码。这符合开闭原则。
观察者模式:实时监控GC行为
GC不是黑盒。我们需要知道它什么时候触发、持续多久、回收了多少内存。观察者模式正好派上用场。
我习惯把GC事件作为被观察的主题,各种监控组件作为观察者。比如,日志记录器、性能计数器、告警系统,都可以订阅GC事件。
// GC事件主题
public class GcEventSubject {
private List<GcObserver> observers = new ArrayList<>();
public void attach(GcObserver observer) {
observers.add(observer);
}
public void detach(GcObserver observer) {
observers.remove(observer);
}
public void notifyObservers(GcEvent event) {
for (GcObserver obs : observers) {
obs.update(event);
}
}
// GC触发时调用
public void fireGcEvent(GcEvent event) {
System.out.println("GC事件触发: " + event.getType());
notifyObservers(event);
}
}
// 观察者接口
public interface GcObserver {
void update(GcEvent event);
}
// 具体观察者:统计GC暂停时间
public class GcPauseMonitor implements GcObserver {
private long totalPauseTime = 0;
private int gcCount = 0;
@Override
public void update(GcEvent event) {
gcCount++;
totalPauseTime += event.getPauseTimeMs();
System.out.println("GC #" + gcCount + " 暂停 " + event.getPauseTimeMs() + "ms");
if (event.getPauseTimeMs() > 100) {
System.out.println("警告:GC暂停时间过长!");
}
}
public double getAveragePauseTime() {
return gcCount == 0 ? 0 : (double) totalPauseTime / gcCount;
}
}
// 另一个观察者:记录GC日志
public class GcLogger implements GcObserver {
@Override
public void update(GcEvent event) {
String log = String.format("[%s] %s: 回收前=%dMB, 回收后=%dMB, 耗时=%dms",
event.getTimestamp(), event.getType(),
event.getBeforeBytes() / 1024 / 1024,
event.getAfterBytes() / 1024 / 1024,
event.getPauseTimeMs());
System.out.println(log);
// 实际项目中写入日志文件
}
}
我曾经在一个金融交易系统中,用观察者模式监控GC。有一次发现GC暂停时间突然飙升到500ms,观察者立刻触发告警。排查后发现是某个定时任务创建了大量临时对象,导致频繁Full GC。如果没有观察者模式,这种问题可能要等到用户投诉才能发现。
小技巧:观察者模式可以结合事件队列使用。如果GC事件非常频繁,不要在观察者回调中做耗时操作,否则会影响GC本身。可以把事件放入队列,异步处理。
状态模式:管理GC的生命周期
GC不是一直运行的。它有多个状态:空闲、标记中、清理中、压缩中、暂停等。不同状态下,GC的行为不同,甚至有些操作在特定状态下是被禁止的。
状态模式可以把这些状态相关的行为封装到独立的状态类中,避免大量的switch-case。
// GC状态接口
public interface GcState {
void handle(GcContext context);
String getStateName();
}
// 空闲状态
public class IdleState implements GcState {
@Override
public void handle(GcContext context) {
System.out.println("GC空闲中,等待触发条件...");
// 检查是否需要触发GC
if (context.shouldTriggerGc()) {
context.setState(new MarkingState());
}
}
@Override
public String getStateName() { return "Idle"; }
}
// 标记状态
public class MarkingState implements GcState {
@Override
public void handle(GcContext context) {
System.out.println("标记阶段:遍历对象图...");
// 执行标记逻辑
context.markLiveObjects();
// 标记完成后进入清理状态
context.setState(new SweepingState());
}
@Override
public String getStateName() { return "Marking"; }
}
// 清理状态
public class SweepingState implements GcState {
@Override
public void handle(GcContext context) {
System.out.println("清理阶段:回收未标记对象...");
context.sweepUnmarkedObjects();
// 清理完成后回到空闲状态
context.setState(new IdleState());
}
@Override
public String getStateName() { return "Sweeping"; }
}
// GC上下文(持有当前状态)
public class GcContext {
private GcState currentState;
public GcContext() {
this.currentState = new IdleState(); // 初始状态
}
public void setState(GcState state) {
System.out.println("GC状态切换: " + currentState.getStateName() + " -> " + state.getStateName());
this.currentState = state;
}
public void run() {
// 不断处理当前状态
while (true) {
currentState.handle(this);
// 模拟时间间隔
try { Thread.sleep(100); } catch (InterruptedException e) { break; }
}
}
// 其他方法...
public boolean shouldTriggerGc() { /* ... */ return true; }
public void markLiveObjects() { /* ... */ }
public void sweepUnmarkedObjects() { /* ... */ }
}
状态模式的好处是,每个状态的行为是自包含的。你可以在标记状态里加一个「暂停检查」逻辑,而不用担心影响到清理状态。我在一个嵌入式系统中用过这个模式,GC状态机非常清晰,后来加了一个「压缩状态」,只需要新增一个类,改改状态转换逻辑就行。
注意:状态模式容易导致状态类过多。如果状态转换逻辑很复杂,可以考虑用状态表(State Table)来管理转换规则,而不是硬编码在状态类里。
三种模式的协同工作
这三种模式不是孤立的。在实际的GC系统中,它们经常配合使用。我画了一张图来展示它们的关系:
从图中可以看出,策略模式负责「做什么」(选择哪种GC算法),状态模式负责「怎么做」(管理GC的执行流程),观察者模式负责「做得怎么样」(监控和反馈)。三者互补,构成了一个完整的GC框架。
实战中的避坑指南
我曾经在一个项目中,把策略模式和状态模式混在一起用,结果状态转换时还要动态切换策略,代码变得非常复杂。后来我吸取了教训:策略模式关注算法选择,状态模式关注流程控制,不要让它们互相依赖。
另外,观察者模式要注意内存泄漏。如果观察者注册后没有及时注销,GC系统会持有观察者的引用,导致观察者无法被回收。我习惯用弱引用(WeakReference)来管理观察者,或者提供一个显式的detach方法。
还有一个坑:GC事件可能非常频繁,尤其是在高压力下。如果每个事件都同步通知所有观察者,可能会拖慢GC本身。我的做法是,在观察者模式中引入一个事件缓冲池,异步批量处理。
总结
设计模式不是纸上谈兵。在垃圾回收这种底层系统中,它们同样能发挥巨大作用。策略模式让GC算法可扩展,观察者模式让GC行为可观测,状态模式让GC流程可管理。这三者结合起来,你就能构建一个灵活、可控、可监控的GC子系统。
下次你在优化GC时,不妨想想:能不能用策略模式替换掉if-else?能不能用观察者模式加上监控?能不能用状态模式理清流程?相信我,代码会变得优雅很多。
一句话总结:策略模式选算法,观察者做监控,状态管流程——设计模式让GC不再神秘。