对象与垃圾回收

以下内容摘自李刚老师《Java疯狂讲义》,其中穿插我个人想法,以此记录,加深印象

垃圾回收机制特征

  • 垃圾回收机制只负责回收堆内存中的对象,不会回收任何物理资源(例如数据库连接、网络IO等资源)。所以诸如数据库连接都是我们调用close方法去释放资源的
  • 程序无法精确控制垃圾回收的运行,垃圾回收会在合适的时候进行。当对象永久性地失去引用后,系统就会在合适的时候回收它所占的内存。也就是说用java是没法精确控制垃圾回收的
  • 在垃圾回收机制回收任何对象之前,总会先调用它的finalize()方法,该方法可能使该对象重新复活(让一个引用变量重新引用该对象),从而导致垃圾回收机制取消回收。

对象在内存中的状态

当一个对象在堆内存中运行时,根据它被引用变量所引用的状态,可以把它所处的状态分成如下三种

  1. 可达状态:当一个对象被创建后,若有一个以上的引用变量引用它,则这个对象在程序中处于可达状态,程序可通过引用变量来调用该对象的Field和方法
  2. 可恢复状态:如果程序中某个对象不再有任何引用变量引用它,它就进入了可恢复状态。在这种状态下,系统的垃圾回收机制准备回收该对象所占用的内存,在回收该对象之前,系统会调用所有可恢复状态对象的finalize()方法进行资源清理。如果系统在调用finalize()方法时重新让一个引用变量引用该对象,则这个对象会再次变为可达状态;否则该对象将进入不可达状态。
    也就是说可恢复状态变为可达状态的唯一机会就是在finalize()方法中再次让该对象被引用
    3.不可达状态:当对象与所有引用变量的关联都被切断,且系统已经调用所有对象的finalize()方法后依然没有使该对象变成可达状态,那么这个对象将永久性地失去引用,最后变成不可达状态。只有当一个对象处于不可达状态时,系统才会真正回收该对象所占有的资源

调用finalize方法不一定进行了垃圾回收,但垃圾回收一定会调用该对象的finalize方法

对象与垃圾回收_第1张图片

强制垃圾回收

当一个对象失去引用后,系统何时调用它的finalize()方法对它进行资源清理,何时它会变成不可达状态,系统何时回收它所占有的内存,对于程序完全透明。程序只能控制一个对象何时不再被任何引用变量引用,绝不能控制它何时被回收。
程序无法精确控制java垃圾回收的时机,但我们依然可以强制系统进行垃圾回收,这种强制只是通知系统进行垃圾回收,但系统是否进行垃圾回收依然不确定

那么这里的强制垃圾回收也只是一个通知而已,就相当于催一下
决定权还是在垃圾回收机制

强制系统垃圾回收有如下两个方法

  1. 调用System类的gc()静态方法:System.gc()
  2. 调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc()
     * @see     java.lang.Runtime#gc()
     */
    public static void gc() {
        Runtime.getRuntime().gc();
    }

由System.gc()方法的源码看到实际上调用的还是Runtime.getRuntime().gc()

所以强制系统垃圾回收直接调用Runtime.getRuntime().gc()就行了

finalize方法

protected void finalize() throws Throwable { }

finalize方法是Object类的实例方法,也就是说任何对象都有该方法
finalize()方法具有如下4个特点

  1. 永远不要主动调用某个对象的finalize()方法,该方法应交给垃圾回收机制调用。
  2. finalize()方法何时被调用,是否被调用具有不确定性,不要把finalize()方法当成一定会被执行的方法。
  3. 当JVM执行可恢复对象的finalize()方法时,可能使该对象或系统中其他对象重新变成可达状态。
  4. 当JVM执行finalize()方法出现异常时,垃圾回收机制不会报告异常,程序继续执行。

下面程序演示了如何在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种方式。

强引用(StrongReference)

这是java程序中最常见的引用方式。程序创建一个对象,并把这个对象赋给一个引用变量,程序通过该引用变量来操作实际的对象。当一个对象被一个或一个以上的引用变量所引用时,它处于可达状态,不可能被系统垃圾回收机制回收。

软引用(SoftReference)

软引用需要通过SoftReference类来实现,当一个对象只有软引用时,它有可能被垃圾回收机制回收。对于只有软引用的对象而言,当系统内存空间足够时,它不会被系统回收,程序也可使用该对象;当系统内存空间不足时,系统可能会回收它。软引用通常用于对内存敏感的程序中。

弱引用(WeakReference)

弱引用通过WeakReference类实现,弱引用和软引用很像,但弱引用的引用级别更低。对于只有弱引用的对象而言,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存。当然,并不是说当一个对象只有弱引用时,它就会立即被回收,正如那些失去引用的对象一样,必须等到垃圾回收机制运行时才会被回收。

那么弱引用几乎等同于失去引用的对象了,只不过弱引用还保留有对象的引用,还可以调用到对象

虚引用(PhantomReference)

虚引用通过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

使用这些引用类可以避免在程序执行期间将对象留在内存中。如果以软引用、弱引用或虚引用的方式引用对象,垃圾收集器就能够随意地释放对象。如果希望尽可能减小程序在其生命周期中所占用的内存大小时,这些引用类就很有用处。

必须指出:要使用这些特殊的引用类,就不能保留对对象的强引用;如果保留了对对象的强引用,就会浪费这些引用类所提供的任何好处。

你可能感兴趣的:(Java基础)