引用计数法会在对象中添加一个引用计数器,每当有一个地方 引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可 能再被使用的。该方法虽然占用了一些额外的内存空间来进行计数,但它的原理非常简单(有引用就加一,为0就是没有被引用过,当一个对象的引用计数器为0时,代表该对象在此时是可以进行回收的),判定效率也很高,在大多数情况下它都是一个不错的算法。但是,在Java 领域,至少主流的Java虚拟机里面都没有选用引用计数算法来管理内存,主要原因是,这个看似简单 的算法有很多例外情况要考虑,必须要配合大量额外处理才能保证正确地工作,譬如单纯的引用计数 就很难解决对象之间相互循环引用的问题。
java中的引用主要是如下四种:
强引用(Strongly Re-ference):·强引用是普通的的“引用”的定义,指在程序代码之中普遍存在的引用赋值,即类似“Object obj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回 收掉被引用的对象,也就是说即使会OOM(内存溢出),被强引用的对象也不会被回收。
软引用(Soft Reference):软引用是用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内 存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存, 才会抛出内存溢出异常。在JDK 1.2版之后提供了SoftReference类来实现软引用,我的理解是,垃圾收集器在扫描到被软引用的对象时不会立即回收,而是会打上一个待定的标签,如果这次回收过程成功结束,那么被软引用的对象会存活,但是如果没有成功结束(中途发生了OOM),就会对软引用的对象进行回收,如果每次垃圾回收都没出现内存溢出之类的情况,那么软引用对象可以一直存活着。
弱引用(Weak Reference):·弱引用也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只 能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只 被弱引用关联的对象。在JDK 1.2版之后提供了WeakReference类来实现弱引用,弱引用和软引用的区别在于,软引用是回收时不产生内存溢出等情况时是不会被回收的,不出现OOM就能一直存活,但是弱引用的对象无论出不出现OOM,在下一次垃圾回收的时候都会被回收。
虚引用(Phantom Reference):虚引用也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。一个对象是否有虚引用的 存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚 引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2版之后提供 了PhantomReference类来实现虚引用。也就是说,这个对象垃圾收集器想回收就回收,回收的时候给虚引用他的对象发条通知就行。
当前主流的商用程序语言(Java、C#等)的内存管理子系统,都是 通过可达性分析(Reachability Analysis)算法来判定对象是否存活的。这个算法的基本思路就是通过 一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过 程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连, 或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的(在我看来就是类似二叉树的前序遍历的一个过程)。
可达性分析算法-三色标记法
在可达性分析算法中,会给扫描到的对象打上一个标记,然后用标记清除、标记整理或者复制算法保留标记过的对象,清除未被标记(不可达)的对象,这里可以知道,对象上的标记只有两个清除或者保留,等此次回收完成之后再把所有的标记清空等待下一次回收。
但是我们想一下,如果与用户线程同时执行会发生什么,同时执行的时候可能在一个对象引用链为扫描完成的时候,用户修改了该对象的引用关系,例如此时对象引用为:a—b—c—d,当扫描到c时,a和b都被标识为保留,此时c到d的引用关系被删除,那么回收器 此时是无法对d对象做出一个准确的判断的,把d判断成回收万一其他用户引用了,就会有空指针的风险,如果不删除d,那就只能把d的标记改为保留,但是如果d没有被其他对象引用了,那么这次的垃圾回收就不会回收d,这就会造成大量的浮动垃圾。
上面回收过程中的问题其实很好解决,只需要添加一个表示中间状态的标记就可以解决这个问题,由于线程同时进行,所以引用链上的对象并不是简单的可达与不可达的关系,而是会有一个扫描过程中的状态,所以就出现了三色标记法。
对三色标记法有兴趣的厚米可以看看这一篇-三色标记法