在Java中,JVM(Java虚拟机)的垃圾识别与分类是自动内存管理的重要组成部分。这一过程主要通过垃圾收集器(Garbage Collector)实现,旨在识别和回收不再被程序引用的对象,以释放内存空间。
1. 垃圾识别与分类的基础
在Java中,所谓的"垃圾"是指没有任何引用指向的对象。这些对象虽然仍然占据着内存空间,但已经无法被程序访问和使用。为了识别和回收这些垃圾对象,JVM采用了多种算法和策略。
2. 垃圾识别算法
2.1 引用计数法
引用计数法是一种简单而直观的垃圾收集算法。其核心思想是通过在对象头中添加一个引用计数器,记录该对象被引用的次数。每当有一个新的引用指向该对象时,引用计数加一;当引用被删除或者超出作用范围时,引用计数减一。当引用计数为零时,表示该对象不再被引用,即可以被回收。
然而,引用计数法有一个明显的缺陷,即难以处理循环引用的情况。例如,两个对象互相引用,它们的引用计数永远不会变为零,即使它们已经不再被程序所使用。
示例代码:
java
class ReferenceCountingObject {
private int referenceCount = 0;
public ReferenceCountingObject() {
// 对象初始化时,引用计数为0
}
public void addReference() {
referenceCount++;
}
public void removeReference() {
referenceCount--;
if (referenceCount == 0) {
// 当引用计数为零时,可以进行垃圾回收操作
System.out.println("对象被回收");
}
}
}
public class ReferenceCountingExample {
public static void main(String[] args) {
// 创建两个对象
ReferenceCountingObject obj1 = new ReferenceCountingObject();
ReferenceCountingObject obj2 = new ReferenceCountingObject();
// obj1引用计数加一
obj1.addReference();
// obj2引用计数加一
obj2.addReference();
// obj1引用计数减一
obj1.removeReference();
// obj1引用计数为零,可以进行垃圾回收
// obj2引用计数仍为一
}
}
2.2 可达性分析算法
可达性分析是Java虚拟机中垃圾收集的核心算法之一。它主要通过判断对象是否能够从一组称为"GC Roots"的根对象出发,通过引用链追踪,最终判断对象是否可达。GC Roots包括虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象以及本地方法栈中JNI(Java Native Interface)引用的对象。
可达性分析的过程如下:
- 初始标记阶段:STW(Stop-The-World),只标记GC根直接关联的对象,耗时极少。
- 并发标记阶段:由GC根依次向下标记所有关联的对象,可达为存活对象,不可达为可回收对象。
- 重新标记阶段:STW,标记新建的对象引用(GC收集器通过读写屏障+增量更新记录新建的引用对象)。
- 清除标记阶段:GC线程与用户线程并发清理被标记的垃圾对象。
可达性分析算法能够解决引用计数法无法处理的循环引用问题,因此成为当前主流虚拟机采用的垃圾收集算法。
3. 垃圾分类与回收算法
在识别出垃圾对象后,JVM需要采用合适的算法来回收这些对象所占用的内存空间。以下是几种常见的垃圾回收算法:
3.1 标记-清除算法
标记-清除算法是最基础的收集算法。它分为"标记"和"清除"两个阶段:
- 标记阶段:标记出所有存活的对象。
- 清除阶段:统一回收所有未被标记的对象。
然而,标记-清除算法存在两个明显的问题:
- 效率问题:如果需要标记的对象太多,效率不高。
- 空间问题:标记清除后会产生大量不连续的碎片。
3.2 复制算法
复制算法将堆内存分割成两块大小相同的区域,每次只使用其中一块进行对象分配。当这一块内存使用完后,就将还存活的对象复制到另一块区域,然后将已使用的内存空间一次清理掉。
复制算法的优点是不会产生内存碎片,但缺点是内存使用效率低,每次只能使用一半的内存空间。
3.3 标记-整理算法
标记-整理算法是对标记-清除算法的优化。它在标记阶段仍然标记所有存活的对象,但在清除阶段不是直接回收对象,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
3.4 分代收集算法
分代收集算法是当前虚拟机普遍采用的垃圾收集算法。它将堆内存划分为新生代和老年代,根据对象的存活周期选择合适的垃圾收集算法。
- 新生代:对象存活率低,通常使用复制算法进行回收。新生代被进一步划分为Eden区、From区和To区,默认内存分配比为8:1:1。
- 老年代:对象存活率高,通常使用标记-清除算法或标记-整理算法进行回收。
4. 垃圾收集器
垃圾收集器是执行垃圾回收操作的组件。在Java中,有多种不同类型的垃圾收集器,包括串行收集器、并行收集器、CMS收集器和G1收集器等。这些收集器采用不同的算法和策略来实现垃圾回收。
4.1 串行收集器(Serial GC)
串行收集器是最基本的垃圾收集器,使用单线程进行垃圾回收。它在进行垃圾收集时,必须暂停其他所有的工作线程,直到收集结束。因此,串行收集器适用于单CPU环境或内存较小的应用。
4.2 并行收集器(Parallel GC)
并行收集器使用多线程进行垃圾回收,提高了回收效率。它同样需要暂停其他工作线程,但回收过程是多线程的,因此适用于多CPU环境或吞吐量要求较高的应用。
4.3 CMS收集器(Concurrent Mark-Sweep GC)
CMS收集器是一种以获取最短回收停顿时间为目标的收集器。它使用并发标记和并发清除的方式来实现垃圾回收,大大减少了停顿时间。然而,CMS收集器在垃圾回收过程中会占用一部分CPU资源,并且可能产生内存碎片。
4.4 G1收集器(Garbage-First GC)
G1收集器是一种面向服务器的垃圾收集器,旨在满足大内存、多处理器的环境下对停顿时间的要求。它将堆内存划分为多个区域(Region),每次只收集一部分区域的垃圾。G1收集器采用并发标记和并发清除的方式,同时结合了复制算法和标记-整理算法的优点,实现了高吞吐量和低停顿时间。
5. 总结
Java JVM中的垃圾识别与分类是自动内存管理的重要组成部分。通过可达性分析算法,JVM能够准确识别出垃圾对象;通过不同的垃圾回收算法和收集器,JVM能够高效地回收这些垃圾对象所占用的内存空间。对于Java开发人员而言,深入理解JVM的垃圾回收机制有助于优化应用性能、解决内存泄漏等问题。