垃圾收集(Garbage Collection,简称GC)简单说,就是要干三件事:
在Java的内存区域中:
现在开始进入垃圾回收的要干的第一件事:哪些内存需要回收?
先来看一种比较古老的方式:引用计数算法(reference counting)。
引用计数器的算法是这样的:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。
这个方法不是目前的主流判定方式,原因除了需要额外的空间来存储计数器,以及繁琐的更新操作,引用计数法还有一个重大的漏洞,那便是无法处理循环引用对象。
举个例子,假设对象 a 与 b 相互引用,除此之外没有其他引用指向 a 或者 b。在这种情况下,a 和 b 实际上已经死了,但由于它们的引用计数器皆不为 0,在引用计数法的心中,这两个对象还活着。因此,这些循环引用对象所占据的空间将不可回收,从而造成了内存泄露。
目前 Java 虚拟机的主流垃圾回收器采取的是可达性分析算法。这个算法的实质在于将一系列 GC Roots 作为初始的存活对象合集(Gc Root Set),然后从该合集出发,探索所有能够被该集合引用到的对象,并将其加入到该集合中,这个过程我们也称之为标记(mark)。最终,未被探索到的对象便是死亡的,是可以回收的。
在Java技术体系里面,固定可作为GC Roots的对象包括以下几种:
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否引用链可达,判定对象是否存活都和“引用”离不开关系。
Java中的引用有四种,分为强引用(Strongly Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱。
Object obj =new Object();
Object obj = new Object();
ReferenceQueue queue = new ReferenceQueue();
SoftReference reference = new SoftReference(obj, queue);
//强引用对象滞空,保留软引用
obj = null;
Object obj = new Object();
ReferenceQueue queue = new ReferenceQueue();
WeakReference reference = new WeakReference(obj, queue);
//强引用对象滞空,保留软引用
obj = null;
Object obj = new Object();
ReferenceQueue queue = new ReferenceQueue();
PhantomReference reference = new PhantomReference(obj, queue);
//强引用对象滞空,保留软引用
obj = null;
要注意,及时对象被判定为不可达,也不一定非死不可。举个不恰当的例子,此时的对象就是秋后问斩的死囚,还有伸冤的机会。
如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记,随后进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。如果对象在在finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可,譬如把自己 (this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它就”逃过一劫“;但是如果没有抓住这个机会,那么对象就真的要被回收了。
参考:
【1】:周志朋编著《深入理解Java虚拟机:JVM高级特性与最佳实践》
【2】:周志朋等翻译《Java虚拟机规范》
【3】:封亚飞编著《揭秘Java虚拟机 JVM设计原理与实现》