5.10 对象与垃圾回收

垃圾回收具有以下特征:
1、垃圾回收机制负责回收堆内存种的对象,不会回收任何物理资源(例如数据库连接、网络IO等资源)。
2、程序无法精确控制垃圾回收的运行,垃圾回收会在合适的时候进行。当对象永久地失去引用后,程序会在合适的时候回收它所占的内存。
3、在垃圾回收机制回收任何对象之前,总会先调用它的finalize()方法,该方法可能使对象重新复活(让一个新的引用变量重新引用该对象),从而导致垃圾回收机制去雄安回收。

一、对象在内存中的状态

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

例如:下面创建了两个字符串对象,并创建了一个引用变量依次指向两个对象。

public class StatusTranfer
{
    public static void test()
    {
        var a=new String("轻量级JAVA EE企业应用开发");//①
        a=new String("疯狂Java讲义");//②
    }
    public static void main(String[] args)
    {
        test();//③
    }
}

当程序执行test()方法的①代码时,代码定义了一个a变量,并让该变量指向"轻量级JAVA EE企业应用开发"的字符串,改代码结束后,"轻量级JAVA EE企业应用开发"字符串对象变成可达状态。
当程序执行②代码时,代码再次创建"疯狂Java讲义"字符串对象,并让a变量指向该对象,此时"轻量级JAVA EE企业应用开发"处于可恢复状态;而"疯狂Java讲义"处于不可状态。
一个对象被另一个方法的局部变量引用时,也可以被其他类的类变量引用或被其他对象的实例变量引用。当某个对象被其它类的类变量引用时,只有当该类被销毁后,该对象才会进入可恢复状态;当某个对象被其它类的实例变量引用时,只有当该对象被销毁后,该对象才会进入可恢复状态。

二、强制垃圾回收

系统无法精确控制Java垃圾回收的时机,但依然可以强制系统进行垃圾回收——这种强制只是通知系统进行垃圾回收,但系统是否进行垃圾回收依然不能确定。
强制系统垃圾回收有两种方式:
1、调用System类的静态方法gc():System.gc()
2、调用Runtime对象的gc()实例方法。
下面程序创建了四个匿名对象,每个对象创建完成后进入可恢复状态,等待系统回收,知道程序退出,系统依然不会回收该资源。

public class GcTest
{
    public static void main(String[] args)
    {
        for(var i=0;i<4;i++)
        {
            new GcTest();
        }
    }
    public void finalize()
    {
        System.out.println("系统正在清理GcTest对象的资源");
    }
}

编译会出现下面提示:
---------- 编译Java ----------
注: GcTest.java使用或覆盖了已过时的 API。
注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。
因为系统何时调用对象的finalize()不确定,因此Java 开始该方法标记为不推荐。
但如果程序改成如下格式:

public class GcTest
{
    public static void main(String[] args)
    {
        for(var i=0;i<4;i++)
        {
            new GcTest();
            //下面两种用法作业相同,强制系统进行垃圾回收
            //System.gc();
            Runtime.getRuntime().gc();
        }
    }
    public void finalize()
    {
        System.out.println("系统正在清理GcTest对象的资源");
    }
}
---------- 运行Java捕获输出窗 ----------
系统正在清理GcTest对象的资源
系统正在清理GcTest对象的资源
系统正在清理GcTest对象的资源

输出完成 (耗时 0 秒) - 正常终止

三、finalize()方法

在没有明确指明清理垃圾资源的情况下,Java提供了默认机制来清理该对象的资源,这个机制就是finalize()方法。该方法定义在Object类里的实例方法,其原型为:
protected void finalize() throw Throwable
当finalize()方法返回后,对象消失,垃圾回收机制开始执行。方法原型中throw Throwable表示它可以抛出任何类型的异常。
Java任何类都可以重写Object类的finalize()方法,在该方法中清理该对象所占用的资源。
finalize方法的四个特点:
1、永远不要主动调用某个对象的finalize方法,该方法应该交由系统的垃圾回收机制调用。
2、finalize()方法何时被调用,是否被调用具有不确定性,不要把finalize()方法当成一定会执行的方法。
3、当JVM执行可恢复对象的finalize()方法时,可能是该对象或则系统中的其他对象重新变成可达状态。
4、当JVM调用finalize()方法出现异常时,垃圾回收机制不会报告异常,程序继续执行。

四、对象的软、弱和虚引用

java.lang.ref包下提供三个类:SoftReference、PhantomReference和WeakReference,它们分别代表了系统对三种对象的引用方式:软引用、虚引用和弱引用。
因此Java语言对象的引用有四种方式:
1、强制引用(StrongReference)
最常见的引用方式,程序创建一个对象,并把这个对象赋给一个引用变量,程序通过该引用变量来操作实际的对象,对象这数组都是这种引用方式。当一个对象被一个或多个引用引用时,它处于可达状态不可能被GC回收。
2、软引用(SoftReferece)
软引用通过SoftReferece类来实现,当一个对象只有弱引用时,它可能被垃圾回收机制回收。对于只有软引用的对象而言,当系统空间足够大时,他不会被系统回收,程序也可使用该对象;当系统空间不足时,系统可能会回收它。软引用通常用于堆内存敏感的程序中。
3、弱引用(WeakReference)
弱引用通过WeakReference类实现,弱引用和软引用很像,但弱引用级别更低。对于只有弱引用的对象而言,当系统的垃圾回收机制运行时,不管系统的内存是否足够,总是回收该对象所占的内存。(当然需要等到垃圾回收机制运行时)
4、虚引用(PhantomReference)
虚引用完全等同于没有引用。甚至对象不能感受到虚引用的存在,如果一个对象只有虚引用时,那么它和没有引用的效果大致相同。虚引用用于跟踪对象被垃圾回收的状态,虚引用不能单独使用,虚引用必须和引用队列(ReferenceQueue)联合引用。
引用队列由java.lang.ref.ReferenceQueue类表示,它用于保存被回收后对象的引用。
(1)当联合使用软引用、弱引用、引用队列时,系统在回收被引用的对象之后,将把被回收对象的引用添加到关联的引用队列中。
(2)与软引用可弱引用不同的是,虚引用在释放之前,就把它对应的虚引用添加到关联的引用队列之中,这使得在对象回收之前采取行动。
软引用和弱引用可以单独使用,但虚引用不可以单独使用,单独使用虚引用没有太大的意义。虚引用的主要作用就是跟踪对象被垃圾回收的状态,程序通过检查与虚引用关联的引用队列中是否已经包含了该虚引用,从而了解所引用的对象是否被回收。
下面程序示范软引用被回收的过程

import java.lang.ref.*;
public class ReferenceTest
{
    public static void main(String[] args)
        throws Exception
    {
        // 创建一个字符串对象
        var str = new String("疯狂Java讲义");//不要使用String str="疯狂Java讲义",系统会使用常量管理池来使用它
        // 创建一个弱引用,让此弱引用引用到"疯狂Java讲义"字符串
        var wr = new WeakReference(str);  // ①
        // 切断str引用和"疯狂Java讲义"字符串之间的引用
        str = null;   // ②
        // 取出弱引用所引用的对象
        System.out.println(wr.get());  // ③
        // 强制垃圾回收
        System.gc();
        System.runFinalization();
        // 再次取出弱引用所引用的对象
        System.out.println(wr.get());  // ④
    }
}
---------- 运行Java捕获输出窗 ----------
疯狂Java讲义
null

输出完成 (耗时 0 秒) - 正常终止

uploading-image-118730.png
程序调用System.gc()和System.runFinalization();通知系统进行垃圾回收,wr是弱引用,不管内存是否够用。弱引用wr指向的对象都将会被回收。因此最后输出wr指向的对象将得到null。
下面程序示范虚引用和引用堆栈的应用

import java.lang.ref.*;
public class PhantomReferenceTest
{
    public static void main(String[] args)
        throws Exception
    {
        //创建一个字符串对象
        var str=new String("疯狂Java讲义");
        //创建一个引用队列
        var rq=new ReferenceQueue();
        //创建一个虚引用引到"疯狂Java讲义"
        var pr=new PhantomReference(str,rq);
        //切断str与"疯狂Java讲义"之间的连接
        str=null;
        //取出虚引用所引用的对象,并不能通过虚引用获取被引用的对象
        System.out.println(pr.get());//①
        System.gc();
        System.runFinalization();
        //垃圾回收之后,虚引用将放到引用队列中
        //取出引用队列中的最先引入的引用与pr进行比较
        System.out.println(rq.poll()==pr);//②

        }
}
---------- 运行Java捕获输出窗 ----------
null
true

输出完成 (耗时 0 秒) - 正常终止

猜你喜欢

转载自www.cnblogs.com/weststar/p/12449076.html
今日推荐