一、垃圾回收为什么需要对象存活判定
想象你家的储物间堆满东西,有些经常用,有些早已积灰。JVM的内存就像这个储物间,需要定期清理"垃圾对象"。但怎么判断哪些对象该留?这就引出了两种经典算法:引用计数和可达性分析。
举个生活例子:
- 引用计数:给每个物品贴便利贴,记录被借用的次数(比如书被3人借阅)
- 可达性分析:检查物品能否从大门出发通过路径找到(比如玄关→客厅→茶几上的遥控器)
二、引用计数算法详解
工作原理
每个对象携带计数器,被引用时+1,引用失效时-1。当计数器归零,立即回收。
(示例代码使用Java技术栈)
public class ReferenceCounting {
Object instance = null;
public static void main(String[] args) {
// 创建两个对象A和B
ReferenceCounting A = new ReferenceCounting();
ReferenceCounting B = new ReferenceCounting();
// 互相引用(计数器变为1)
A.instance = B;
B.instance = A;
// 断开外部引用(但A和B仍在互相引用)
A = null;
B = null;
/* 此时:
A和B的引用计数均为1
但实际已是垃圾对象 */
}
}
优缺点分析
优点:
- 实时回收:计数器归零立刻清理,像超市货架即时补货
- 低延迟:不需要全局扫描
缺点:
- 循环引用问题:像两个死党互相说"你先走",结果都走不掉
- 频繁更新计数器:高并发时像超市收银台排长队
三、可达性分析算法详解
工作原理
从"GC Roots"(相当于地标建筑)出发,所有能走通的路径上的对象都存活,其余回收。GC Roots包括:
- 栈帧中的局部变量
- 静态变量
- JNI引用
(示例代码使用Java技术栈)
public class ReachabilityAnalysis {
static class Node {
String name;
Node next;
Node(String name) { this.name = name; }
}
static Node globalHead = new Node("总部"); // GC Root
public static void main(String[] args) {
Node branchA = new Node("分公司A");
Node branchB = new Node("分公司B");
// 构建引用链
globalHead.next = branchA;
branchA.next = branchB;
// 断开branchB的引用
branchA.next = null;
/* GC工作时:
- globalHead → branchA 存活
- branchB 不可达被回收 */
}
}
进阶应用:四种引用类型
- 强引用:像铁链连接(new创建的对象)
- 软引用:像橡皮筋连接,内存不足时断开(适合缓存)
- 弱引用:像棉线连接,GC即断开(实现WeakHashMap)
- 虚引用:像透明胶带,仅用于回收跟踪
四、两种算法的实战对比
场景对决
| 场景 | 引用计数表现 | 可达性分析表现 |
|---|---|---|
| 循环引用 | 内存泄漏 | 正确回收 |
| 高频创建/销毁 | 计数器成性能瓶颈 | 批量处理更高效 |
| 大内存应用 | 实时回收优势明显 | 停顿时间可能较长 |
现代JVM的选择
主流JVM(HotSpot等)都采用可达性分析,因为:
- 准确解决循环引用问题
- 配合分代收集更高效(年轻代用复制算法,老年代用标记-整理)
但某些场景仍会结合引用计数思想,比如:
- Python的垃圾回收机制
- Objective-C的ARC内存管理
五、开发中的注意事项
避免内存泄漏:
- 及时清除无用的监听器(如EventBus注册/反注册成对出现)
- 小心静态集合(像永不清理的公告栏)
优化技巧:
// 不好的做法:频繁创建临时对象 for(int i=0; i<10000; i++) { String temp = new String("item" + i); // 产生大量垃圾 } // 改进方案:重用对象或使用基础类型 StringBuilder sb = new StringBuilder(); for(int i=0; i<10000; i++) { sb.setLength(0); sb.append("item").append(i); // 复用对象 }监控工具:
- jstat观察GC频率
- VisualVM分析对象引用链
六、总结与选择建议
引用计数像精打细算的会计,可达性分析像全局视角的侦探。现代Java开发中:
- 99%的情况交给JVM的可达性分析
- 剩余1%需要关注特殊引用类型的使用
记住关键原则:让对象引用链尽可能短,像整理房间时减少物品的依赖关系。当你能清晰说出每个对象的"存活理由",就真正掌握了垃圾回收的精髓。
评论