首发于CoffeeCodes
JVM-可达性分析算法

JVM-可达性分析算法

垃圾回收

垃圾回收,就是将已经分配出去的,但却不再使用的内存收回来,以便能搞再次分配。

在Java虚拟机的语境下,垃圾指的是死亡的对象所占据的堆空间。

如何辨别一个对象是存是亡?

一种古老的辨别方法: 可以通过引用计数法(reference counting),为每个对象添加一个引用计数器,用来统计指向该对象的引用个数。 一旦某个对象的引用计数器为0,则说明该对象已经死亡,便可以被回收了。

目前Java虚拟机的主流垃圾回收器采取的是可达性分析算法。

这个算法的实质在于将一系列GC Roots作为初始的存活对象合集(live set),然后从该合集出发,探索所有能够被该合集引用到的对象,并将其加入到该和集中,这个过程称之为标记(mark)。 最终,未被探索到的对象便是死亡的,是可以回收的。

引用计数法的实现

如果有一个引用,被赋值为某一对象,那么将该对象的引用计数器+1。 如果一个指向某一对象的引用,被赋值为其他值,那么将该对象的引用计数器-1。 需要截获所有的引用更新操作,并且相应地增减目标对象的引用计数器。

引用计数法的缺点

一是需要额外的空间来存储计数器,以及繁琐的更新操作。

二是无法处理循环引用对象。

假设对象a与b相互引用,除此之外没有其他引用指向a或者b。 在这种情况下,a和b实际上已经死了,但由于它们的引用计数器皆不为0,在引用计数器的心中,这两个对象还活着。 因此,这些循环引用对象所占据的空间将不可回收,从而造成了内存泄漏。

GC Roots

GC Roots可以理解为由堆外指向堆内的引用, 一般而言,GC Roots包括(但不限于)以下几种:

  1. Java 方法栈桢中的局部变量;
  2. 已加载类的静态变量;
  3. JNI handles;
  4. 已启动且未停止的 Java 线程。

可达性分析算法的优点

可达性分析可以解决引用计数器所不能解决的循环引用问题。

即便对象a和b相互引用,只要从GC Roots出发无法到达a或者b,那么可达性分析便不会将它们加入存活对象合集之中。

可达性分析算法的不足

在多线程环境下,其他线程可能会更新已经访问过的对象中的引用,从而造成误报(将引用设置为null)或者漏报(将引用设置为未被访问过的对象)。

误报并没有什么伤害,Java虚拟机至多损失了部分垃圾回收的机会。

漏报则比较麻烦,因为垃圾回收器可能回收事实上仍被引用的对象内存。 一旦从原引用访问已经被回收了的对象,则很有可能会直接导致Java虚拟机崩溃。

发布于 02-26

文章被以下专栏收录