以下内容摘自李刚老师《Java疯狂讲义》,其中穿插我个人想法,以此记录,加深印象
所以诸如数据库连接都是我们调用close方法去释放资源的
也就是说用java是没法精确控制垃圾回收的
在垃圾回收机制回收任何对象之前,总会先调用它的finalize()方法
,该方法可能使该对象重新复活(让一个引用变量重新引用该对象),从而导致垃圾回收机制取消回收。当一个对象在堆内存中运行时,根据它被引用变量所引用的状态,可以把它所处的状态分成如下三种
也就是说可恢复状态变为可达状态的唯一机会就是在finalize()方法中再次让该对象被引用
只有当一个对象处于不可达状态时,系统才会真正回收该对象所占有的资源
调用finalize方法不一定进行了垃圾回收,但垃圾回收一定会调用该对象的finalize方法
当一个对象失去引用后,系统何时调用它的finalize()方法对它进行资源清理,何时它会变成不可达状态,系统何时回收它所占有的内存,对于程序完全透明。程序只能控制一个对象何时不再被任何引用变量引用,绝不能控制它何时被回收。
程序无法精确控制java垃圾回收的时机,但我们依然可以强制系统进行垃圾回收,这种强制只是通知系统进行垃圾回收,但系统是否进行垃圾回收依然不确定
。
那么这里的强制垃圾回收也只是一个通知而已,就相当于催一下
决定权还是在垃圾回收机制
强制系统垃圾回收有如下两个方法
* @see java.lang.Runtime#gc()
*/
public static void gc() {
Runtime.getRuntime().gc();
}
由System.gc()方法的源码看到实际上调用的还是Runtime.getRuntime().gc()
所以强制系统垃圾回收直接调用Runtime.getRuntime().gc()就行了
protected void finalize() throws Throwable { }
finalize方法是Object类的实例方法,也就是说任何对象都有该方法
finalize()方法具有如下4个特点
下面程序演示了如何在finalize方法里复活自身,并可通过该程序看出垃圾回收的不确定性。
public class FinalizeTest {
private static FinalizeTest ft;
public void info(){
System.out.println("测试资源清理的finalize方法");
}
public void finalize(){
//当ft引用到试图回收的可恢复对象,可恢复对象重新变成可达状态
ft = this;
}
public static void main(String[] args) throws InterruptedException {
//创建FinalizeTest对象立即进入可恢复状态
new FinalizeTest();
//通知系统进行资源回收,催一下
Runtime.getRuntime().gc();
//让程序暂停2秒
Thread.sleep(2000);
ft.info();
}
}
由此也可以看出垃圾回收机制是专门的一个线程去执行的
如果不加Thread.sleep(2000);
这行代码程序可能会报空指针异常,因为不知道系统何时会进行垃圾回收,所以不一定会调用finalize方法
除此之外,System和Runtime类里都提供了一个runFinalization方法,可以强制垃圾回收机制调用系统中可恢复对象的finalize方法。
public class FinalizeTest {
private static FinalizeTest ft;
public void info(){
System.out.println("测试资源清理的finalize方法");
}
public void finalize(){
//当ft引用到试图回收的可恢复对象,可恢复对象重新变成可达状态
ft = this;
}
public static void main(String[] args) {
//创建FinalizeTest对象立即进入可恢复状态
new FinalizeTest();
//通知系统进行资源回收,催一下
Runtime.getRuntime().gc();
Runtime.getRuntime().runFinalization();
ft.info();
}
}
Runtime.getRuntime().runFinalization();这行代码会强制垃圾回收机制调用可恢复对象的finalize方法。该方法执行结束后,ft将引用到系统试图回收的FinalizeTest对象,所以程序可以正常执行ft的info方法
对大部分对象而言,程序里会有一个引用变量引用该对象,这是最常见的引用方式。除此之外,java.lang.ref包下提供了3个类:SoftReference、PhantomReference和WeakReference,它们分别代表了系统对对象的3种引用方式:软引用、虚引用和弱引用。因此,java语言对对象的引用有如下4种方式。
这是java程序中最常见的引用方式。程序创建一个对象,并把这个对象赋给一个引用变量,程序通过该引用变量来操作实际的对象。当一个对象被一个或一个以上的引用变量所引用时,它处于可达状态,不可能被系统垃圾回收机制回收。
软引用需要通过SoftReference类来实现,当一个对象只有软引用时,它有可能被垃圾回收机制回收。对于只有软引用的对象而言,当系统内存空间足够时,它不会被系统回收,程序也可使用该对象;当系统内存空间不足时,系统可能会回收它
。软引用通常用于对内存敏感的程序中。
弱引用通过WeakReference类实现,弱引用和软引用很像,但弱引用的引用级别更低。对于只有弱引用的对象而言,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存。当然,并不是说当一个对象只有弱引用时,它就会立即被回收,正如那些失去引用的对象一样,必须等到垃圾回收机制运行时才会被回收。
那么弱引用几乎等同于失去引用的对象了,只不过弱引用还保留有对象的引用,还可以调用到对象
虚引用通过PhantomReference类实现,虚引用类似于没有引用。虚引用对对象本身没有太大影响,对象甚至感觉不到虚引用的存在。如果一个对象只有一个虚引用时,那么它和没有引用的效果大致相同。虚引用主要用于跟踪对象被垃圾回收的状态,虚引用不能单独使用,虚引用必须和引用队列(ReferenceQueue)联合使用
。
也就是说对象被软引用、弱引用、虚引用 引用时是可以被垃圾回收的,而被强引用引用时是不可以被垃圾回收的,这就是区别
软引用与弱引用区别是软引用在内存足够的时候不会被回收
弱引用不管内存是否足够都要被垃圾回收机制回收
虚引用与以上两种引用不同的是
虚引用不能单独使用必须与引用队列结合使用
下面程序示范了弱引用所引用的对象被系统垃圾回收的过程
public class ReferenceTest {
public static void main(String[] args) {
//创建一个字符串对象
String str = new String("疯狂Java讲义");
//创建一个弱引用,让此弱引用引用到“疯狂Java讲义”字符
WeakReference wr = new WeakReference(str);
//切断str引用和“疯狂Java讲义”字符串之间的引用
str = null;
//取出弱引用所引用的对象
System.out.println(wr.get());
//强制垃圾回收
System.gc();
System.runFinalization();
//再次取出弱引用所引用的对象
System.out.println(wr.get());
}
}
运行程序结果如下
疯狂Java讲义
null
这里也验证了被弱引用引用的对象依然会被垃圾回收机制回收
从下面这行源码可以看到弱引用实际上就是一个强引用,Treated specially by GC这句话的意思就是被GC特殊对待
private T referent; /* Treated specially by GC */
public class PhantomReferenceTest {
public static void main(String[] args) {
//创建一个字符串对象
String str = new String("疯狂Java讲义");
//创建一个引用队列
ReferenceQueue rq = new ReferenceQueue();
//创建一个虚引用,让此虚引用引用到“疯狂Java讲义”字符串
PhantomReference pr = new PhantomReference(str,rq);
//切断str引用和“疯狂Java讲义”字符串之间的引用
str = null;
//取出虚引用所引用的对象
//并不能通过虚引用访问被引用的对象,所以此处输出null
System.out.println(pr.get());//1
//强制垃圾回收
System.gc();
System.runFinalization();
//垃圾回收之后,虚引用将被放入引用队列中
//取出引用队列中最先进入队列的引用与pr进行比较
System.out.println(rq.poll() == pr);//2
}
}
因为系统无法通过虚引用来获得被引用的对象,所以执行1处的输出语句时,程序将输出null(及时此时并未强制进行垃圾回收)。当程序强制垃圾回收后,只有虚引用引用的字符串对象会被垃圾回收,当被引用的对象被回收后,对应的虚引用将被添加到关联的引用队列中,因而将在2处看到输出true
使用这些引用类可以避免在程序执行期间将对象留在内存中。如果以软引用、弱引用或虚引用的方式引用对象,垃圾收集器就能够随意地释放对象。如果希望尽可能减小程序在其生命周期中所占用的内存大小时,这些引用类就很有用处。
必须指出:要使用这些特殊的引用类,就不能保留对对象的强引用;如果保留了对对象的强引用,就会浪费这些引用类所提供的任何好处。