一、判断垃圾的算法
判断对象是否为垃圾的核心是确定对象是否不再被使用。Java主要采用以下两种算法:
1. 引用计数法(Reference Counting)
-
原理:每个对象维护一个引用计数器,记录被引用的次数。当引用被添加时计数器加1,引用失效时减1。当计数器为0时,对象被视为垃圾。
-
缺点:
-
无法解决循环引用问题(例如:对象A和B互相引用,但无外部引用)。
-
-
Java未采用此算法,因为循环引用会导致内存泄漏。
2. 可达性分析算法(Reachability Analysis)
-
原理:从GC Roots(一组根对象)出发,遍历所有可达对象。未被遍历到的对象视为不可达,标记为垃圾。
-
GC Roots包括:
-
虚拟机栈(栈帧中的局部变量)中引用的对象。
-
方法区中静态变量(
static
)引用的对象。 -
本地方法栈(JNI)中引用的对象(Native方法)。
-
Java虚拟机内部对象(如基本类型的Class对象)。
-
-
优点:解决了循环引用问题。
-
注意:即使对象不可达,也可能在
finalize()
方法中“复活”,但此方法不推荐使用。
3. 引用类型的影响
-
强引用(Strong Reference):普通引用(如
Object obj = new Object()
),只要存在,对象不会被回收。 -
软引用(Soft Reference):内存不足时被回收,适合缓存。
-
弱引用(Weak Reference):下一次GC时被回收,适合临时缓存。
-
虚引用(Phantom Reference):无法通过虚引用获取对象,仅用于跟踪回收状态。
二、垃圾回收算法
Java通过不同算法实现垃圾回收,核心算法如下:
1. 标记-清除(Mark-Sweep)
-
步骤:
-
标记:遍历所有对象,标记存活对象。
-
清除:回收未标记的对象。
-
-
缺点:
-
内存碎片化,影响大对象分配。
-
效率不稳定(对象越多,标记和清除越耗时)。
-
2. 复制算法(Copying)
-
步骤:将内存分为两块(如
Eden
和Survivor
区),每次使用一块。存活对象复制到另一块,清空原块。 -
优点:无内存碎片,适合存活率低的新生代。
-
缺点:内存利用率仅50%(需预留空间)。
3. 标记-整理(Mark-Compact)
-
步骤:
-
标记:同标记-清除。
-
整理:将存活对象向内存一端移动,清除边界外的空间。
-
-
优点:避免碎片化,适合老年代。
-
缺点:移动对象成本较高。
4. 分代收集算法(Generational Collection)
-
核心思想:根据对象存活周期将堆划分为新生代(Young Generation)和老年代(Old Generation)。
-
新生代(存活率低):使用复制算法(如
Eden
和Survivor
区)。 -
老年代(存活率高):使用标记-清除或标记-整理算法。
-
-
触发条件:
-
Minor GC:清理新生代。
-
Major GC/Full GC:清理老年代,通常伴随STW(Stop-The-World)暂停。
-
三、垃圾收集器
不同垃圾收集器实现了上述算法,常见的有:
-
Serial:单线程,适合客户端应用。
-
Parallel:多线程,注重吞吐量。
-
CMS(Concurrent Mark-Sweep):并发标记清除,减少停顿时间。
-
G1(Garbage-First):分区域收集,兼顾吞吐量和低延迟。
-
ZGC/Shenandoah:超低延迟(暂停时间<10ms),适用于大内存场景。
总结
-
判断垃圾:Java通过可达性分析(GC Roots)识别不可达对象。
-
回收算法:根据对象生命周期使用分代收集,结合标记-清除、复制和标记-整理算法。
-
优化方向:权衡内存碎片、吞吐量和延迟,选择适合的垃圾收集器(如G1/ZGC)。