在jvm垃圾回收器中判断对象是否存活的方法是引用计数器算法可达性分析算法,它也是jvm垃圾回收算法的前提。

垃圾回收算法是标记清除算法标记整理算法标记复制算法

下面分别对这2种存活算法和3种垃圾回收算法进行讲解。垃圾收集器请参考  JVM垃圾收集器

引用计数器算法

JVM 引用计数器算法

引用计数器算法的原理是给每个运行的对象添加一个引用计数器,引用一次,计数器加1,引用失效,计数器减1,当计数器为0的时候,就被认为不可能再引用。

这种方式的实现简单,但是存在一个循环引用的问题。

例如

JVM 循环引用

A a = new A();
B b = new B();
a.b = b; 
b.a = a;

上面的例子中,a的b变量指向对象b,b的a变量指向对象a,这样引用计数器认为a和b对象的引用数永远不会变0。

这也是主流jvm没有选择这一算法的原因,为了解决这一问题,出现了可达性分析算法。

可达性分析算法

JVM 可达性分析算法

为了解决循环引用的问题,在jvm垃圾收集器中使用了可达性分析算法。

在可达性分析算法中,通过一个叫做 GC Roots 的对象为起点向下搜索,如果一个对象到GC Roots没有任何引用链相连接时,说明此对象不可用。

Java中可以作为GC Roots的对象

  1. 虚拟机栈(javaStack)(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
  2. 方法区中的类静态属性引用的对象。
  3. 方法区中常量引用的对象。
  4. 本地方法栈中JNI(Native方法)引用的对象。

举例说明

1)虚拟机栈的对象

public class HelloWorld {
	public static void main(String[] args) {
		HelloWorld helloWorld = new HelloWorld();
		helloWorld = null;
	}
}
上面的代码中,helloWorld 是栈帧中的本地变量,当 helloWorld = null 时,由于此时 helloWorld 充当了 GC Roots 的作用,helloWorld 与原来指向的实例 new HelloWorld() 断开了连接,所以对象会被回收。

2)方法区中类静态属性引用的对象

public class HelloWorld {
	public static HelloWorld hello;
	public static void main(String[] args) {
		HelloWorld helloWorld = new HelloWorld();
		helloWorld.hello = new HelloWorld();
		helloWorld = null;
	}
}
上面的代码中,当栈帧中的本地变量 helloWorld = null 时,由于 helloWorld 原来指向的对象与 GC Roots (变量 helloWorld) 断开了连接,所以 helloWorld 原来指向的对象会被回收,而由于我们给 hello 赋值了变量的引用,hello 在此时是类静态属性引用,充当了 GC Root 的作用,它指向的对象依然存活!
3)方法区中常量引用的对象
public class HelloWorld {
	public static final HelloWorld hello = new HelloWorld();
	public static void main(String[] args) {
		 HelloWorld helloworld = new HelloWorld();
	         helloworld = null;
	}
}

上面的代码中,常量 hello 指向的对象并不会因为 helloworld 指向的对象被回收而回收。

下面介绍jvm中的几种回收算法。

标记清除算法

JVM 标记清除算法

JVM标记清除算法分为两步

  1. 标记:标记从根节点被引用的对象。
  2. 清除:清除未被引用的对象。

优缺点如下

  • 优点:解决了引用计数器的循环引用问题。
  • 缺点:会产生不连续的内存碎片;效率上不高,因为标记和清除需要遍历所有对象。

标记整理算法

JVM 标记整理算法

为了解决标记清除算法内存碎片问题,标记整理算法出现了。

它也是分成2个步骤:

  1. 标记:和标记清除一样,标记从根节点被引用的对象。
  2. 整理:整理内存不连续的对象,将对象向一端压缩,清理边界外的垃圾。

优缺点如下:

  • 优点:解决了标记清除算法内存碎片问题。
  • 缺点:多了一个压缩的步骤,增加了GC的时间。

标记复制算法

JVM 标记复制算法

为了解决标记整理算法的效率问题,标记复制算法出现了。

标记复制算法的原理是将内存一分为二,每次只用其中一块内存区域,在进行垃圾回收的时候,将存活的对象复制到另外一片内存区域。

如图所示,整理前使用左边的内存区域,右边的区域留着备用,整理后将存活的对象复制到右边的区域,减少了GC整理内存碎片的时间,典型的空间换时间。

了解JVM 垃圾回收算法后请看 JVM垃圾收集器