一. 简介
垃圾回收(Garbage Collection,简称GC),是Java体系最重要的组成成分之一。GC中的垃圾,特指存在于内存中的、不会再被使用的对象,而“回收”,也相当于把垃圾桶“倒掉”,这样内存空间里就会有空闲的区域被腾出来。如果不及时对内存中的垃圾进行清理,那么,这些垃圾对象所占用的内存空间会一直保留到应用程序结束,被保留的空间无法被其他对象使用。如果大量不会被使用的对象一直占着空间不放,需要内存空间时,就无法使用这些被垃圾对象占用的内存,从而有可能导致内存溢出。因此,对内存空间的管理来说,识别和清理垃圾对象是至关重要的。
垃圾回收的基本思想是考察每一个对象的可触及性,即从根节点开始是否可以访问到这个对象,如果可以,则说明当前对象正在被使用,如果从所有的根节点都无法访问到某个对象,说明对象已经不再使用了,一般来说, 此对象需要被回收。但事实上,一个无法触及的对象有可能在某一个条件下“复活”自己,如果这样,那么对它的回收就是不合理的,为此,需要给出一个对象可触及性状态的定义,并规定在什么状态下,才可以安全地回收对象。
简单来说,可触及性可以包含以下3种状态:
①可触及的:从根节点开始,可以到达这个对象。
②可复活的:对象的所有引用都被释放,但是对象有可能在finalize()函数中复活。
③不可触及的:对象的finalize()函数被调用,并且没有复活,那么就会进入不可触及状态,不可触及的对象不可能被复活,因为finalize()函数只会被调用一次。
二. 对象的复活
在前面说到,对象有可能在finalize()函数中复活自己,看下面例子:
public class CanReliveObj {
public static CanReliveObj obj;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("CanReliveObj finalize called");
obj = this;
}
@Override
public String toString() {
return "I am CanReliveObj";
}
public static void main(String[] args) throws InterruptedException {
obj = new CanReliveObj();
obj = null;
System.gc();
Thread.sleep(1000);
if (obj == null) {
System.out.println("obj 是 null");
} else {
System.out.println("obj 可用");
}
System.out.println("第二次gc");
obj = null;
System.gc();
Thread.sleep(1000);
if (obj == null) {
System.out.println("obj 是 null");
} else {
System.out.println("obj 可用");
}
}
}
上面程序运行结果如下:
CanReliveObj finalize called
obj 可用
第二次gc
obj 是 null
可以看到,在第一次执行System.gc()前,虽然执行了obj=null清除对象的引用,但在调用finalize()函数时,对象的this引用依然会被传入方法内部,如果引用外泄,对象obj就可以复活,此时,对象又变为可触及状态。而finalize()函数只会被调用一次,因此,第二次清楚对象时,对象就再无机会复活,因此就会被回收。
三. 引用和可触及性的强度
在Java中提供了4个级别的引用:强引用、软引用、弱引用和虚引用。强引用就是程序中一般使用的引用类型,强引用的对象时可触及的,不会被回收。相对的,软引用、弱引用和虚引用的对象时软可触及、弱可触及和虚可触及的,在一定条件下,都是可以被回收的。
3.1 强引用 -- 不被回收
给出一个强引用的例子:
StringBuffer str = new StringBuffer("Hello world");
StringBuffer str1 = str;
假设以上代码是在函数体内运行的,那么局部变量str将被分配在栈上,而对象StringBuffer实例将被分在堆上。局部变量str指向StringBuffer实例所在堆空间,通过str可以操作该实例,那么str就是StringBuffer实例的强引用。而赋值语句使得str所指向的对象也被str1所指向,此时,该StringBuffer实例就有两个引用,这两个引用都是强引用。
强引用具备以下特点:
①强引用可以直接访问目标对象。
②强引用所指向的对象在任何时候都不会被系统回收,虚拟机宁愿抛出OOM异常也不会回收强引用所指向对象。
③强引用可能导致内存泄漏。
3.2 软引用 -- 可被回收的引用
软引用是比强引用弱一点的引用类型。一个对象只持有软引用,那么当堆空间不足时,就会被回收。GC未必会回收软引用的对象,但是,当内存资源紧张时,软引用对象会被回收,所以软引用对象不会引起内存溢出。
每一个软引用都可以附带一个引用队列,当对象的可达性状态发生改变时(由可达变为不可达),软引用对象就会进入引用队列。通过这个引用队列,可以跟踪对象的回收情况。
3.3 弱引用 -- 发现即回收
弱引用是一种比软引用较弱的引用类型。在系统GC时,只要发现弱引用,不管系统堆空间使用情况如何,都会将对象进行回收。但是,由于垃圾回收器的线程通常优先级很低,因此并不一定能很快地发现持有弱引用的对象,在这种情况下,弱引用对象可以存在较长的时间。一旦一个弱引用对象被垃圾回收器回收,便会加入到一个注册的引用队列中。
软引用、弱引用都非常适合来保存那些可有可无的缓存数据。如果这么做,当系统内存不足时,这些缓存数据会被回收,不会导致内存溢出,而当内存资源充足时,这些缓存数据又可以存在相当长的时间,从而起到加速系统的作用。
3.4 虚引用 -- 对象回收跟踪
虚引用是所有引用类型中最弱的一个。虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。
当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象后将这个虚引用加入引用队列,以通知应用程序对象的回收情况。由于虚引用可以跟踪对象的回收时间,因此,也可以将一些资源释放操作放置在虚引用中执行和记录。