课程咨询 :18487146383

  • 浅谈Java中的引用

    发布:昆明Java培训      来源:达内新闻      时间:2016-12-30

  • 昆明Java培训的老师这一期给大家讲在Java语言中,引用是指,某一个数据,代表的是另外一块内存的的起始地址,那么我们就称这个数据为引用。

    在JVM中,GC回收的大致准则,是认定如果不能从根节点,根据引用的不断传递,最终指向到一块内存区域,我们就将这块内存区域回收掉。但是这样的回收原则未免太过粗暴。有些时候,内存的使用并不紧张,我们并不希望GC那么勤劳的、快速的回收掉内存。反而有时候希望数据可以在内存中尽可能的保留长一会,待到虚拟机内存吃紧的时候,再来清理掉他。因此从JDK1.2之后,引用的类型变的多样化,从而更好的适应编码的需要。

    下面次来介绍下四种引用:

    1、强引用Strong Reference

    这是Java程序中,最普遍的一种引用。

    程序创建一个对象,并且把这个对象赋值给一个引用变量,我们就称这个引用变量为强引用。很多书上说,强引用是不会被GC回收掉的,个人觉得这话是需要背景的:即强引用变量所处的位置,一定是在GC回收,所判定的Root节点能够依次传递到的引用,如果出现孤立的循环引用。那么即使对象中,存在强引用,也一定是会被回收掉的。其次需要强调的就是强引用不被回收,一定要(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )处在强引用所在的作用域中,如方法栈已经弹出,那么栈帧中的局部变量表中的变量就会被回收,其中存在的强引用的指向关系也会被解除。当然叨叨这么多,只是想说,强引用是否被回收,一定要看具体的情况,而不能一概而论。

    笔者认为,引用,就类似于生活中对物品的持有状态,如果一件物品,对我们至关重要,是必不可少的,无论如何打扫卫生我们都不可能会清理掉他们,那么这种关系状态,我们就认为是强引用。

    2、软引用Soft Reference

    当一个对象的引用关系一直保留,GC就不会清理掉这个对象,我们称之为强引用。在平常的开发中,我们还希望有这样一种引用状态:只要内存够用,即使GC进行回收,我们仍然会一直保留,反之倘若内存不够用,那么下次GC回收时,就会处理掉强引用所指向的对象。

    强引用可以理解为GC永远不会强制删除的引用,而软引用,则可以理解为,家中存放的可有可无的物件,比如可有可无的废弃的家具、电脑中已经不会再使用的软件、手机上保存的可能不会再翻阅浏览的信息、照片、视频。(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )对于这些东西,只要家中仍然有剩余的空间,手机中仍然有足够的硬盘空间,大部分人都会一直保留,直到手机废弃、家中拆迁。但是倘若,手机的硬盘空间开始吃紧、家中没有剩余的空间可供使用,很多人就会选择一次性的,把这些没用的东西全部都处理掉,尽管这些东西可能在以前的清理过程中,一直被保留。

    Java的这种设计,正是为了模拟类似于生活中,对于鸡肋物件的关系。如果保存空间足够,那么久保留该物件,如果保存空间不足,那么才开始清理。

    3、弱引用Weak Reference

    软引用让Jvm的内存管理,拥有弹性,可以根据使用情况动态的调整要回收的对象。弱引用与软引用的性质类似。不同之处在于,对于弱引用指向的对象,无论内存是否够用,下次GC回收时,都会回收掉该块内存。这就像我们平常打扫卫生,有些东西一直在使用,但是倘若要打扫卫生了,就一定会处理掉这些物品。即使房间内仍然有足够的空间,可以保留这些物品。

    对于软引用和弱引用,在使用时,不可以再像原有强引用一般,直接给引用变量赋值,否则强引用关系会再次建立。这里则需要依赖java.lang.ref包下的几个类,使用方法可以参考如下代码:

    1 import java.lang.ref.SoftReference;

    2 import java.lang.ref.WeakReference;

    3

    4 public class RefLearn

    5 {

    6    

    7    private static WeakReference<RefLearn> weakRef0;

    8    private static WeakReference<RefLearn> weakRef1;

    9    

    10    public static void main(String arg[]) throws InterruptedException

    11    {

    12        RefLearn refLearn = new RefLearn();

    13        refLearn.init();

    14    }

    15    

    16    private void init() throws InterruptedException

    17    {

    18        RefLearn obj = new RefLearn();

    19        SoftReference<RefLearn> softRef = new SoftReference<RefLearn>(obj);//将实例传进来

    20        obj = null;//切断原有的强引用,一般使用时,直接在构造函数中new即可

    21        weakRef0 = new WeakReference<RefLearn>(new RefLearn());

    22        weakRef1 = new WeakReference<RefLearn>(new RefLearn());

    23        softRef.get().doNothing();

    24        if (weakRef0.get() != null)

    25        {

    26 //           System.gc();

    27 //           Thread.sleep(2000);

    28            Thread.sleep(1000);

    29            weakRef0.get().doSomething();

    30        }

    31        // obj = weakRef.get();//这里会再次建立强引用,会阻止回收

    32    }

    33    

    34    private void doNothing()

    35    {

    36        

    37    }

    38    

    39    private void doSomething()

    40    {

    41        int i = 0;

    42        while (true)

    43        {

    44            try

    45            {

    46                System.gc();

    47                Thread.sleep(1000);

    48                boolean relate0 = weakRef0.get() == null;

    49                boolean relate1 = weakRef1.get() == null;

    50                System.out.println(relate0 + " " + relate1 + " " + i++);

    51            }

    52            catch (InterruptedException e)

    53            {

    54            }

    55        }

    56    }

    57 }

    代码中通过引用类的get()方法,可以获取到非强引用所指向的变量,同时使用它们。

    通过上述代码,我们会发现两个问题

    问题一

    如果内存吃紧,那么是所有软引用都被回收,还是只回收尽可能少的软引用?

    答案是后者,建立软引用后,引用对象会被打一个时间戳,标记该引用当前所处的GC时间,也就是这两个时间:

    1    /**

    2     * Timestamp clock, updated by the garbage collector

    3     */

    4   static private long clock;

    5    /**

    6     * Timestamp updated by each invocation of the get method. The VM may use

    7     * this field when selecting soft references to be cleared, but it is not

    8     * required to do so.

    9     */

    10    private long timestamp;

    当该软引用每次被调用get时,都会修改该引用的时间戳,来标识该引用指向变量的最后调用时间。GC会在回收过程中,优先回收一直不被使用的软引用。

    问题二(这是一道大题,有两个小问(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ ))

    (1)在判断get()的取值是否为null后,再次使用时,如果这个期间,内存被回收了,怎么办?

    (2)如果在执行弱引用执行的方法时,内存是否会被回收掉?

    问题(1)的情况显然会抛出空引用异常

    因此在使用软引用和弱引用时,务必要注意空引用异常。

    问题(2)的情况我们可以看示例代码的运行结果:

    通过实现可以知道,如果正在执行一个弱引用所指向内存的方法,那么这个虚弱引用是可以逃过这段时间内GC的回收的。其实想想也该明白,方法参数其实默认有this变量作为参数(这个以后会说)。同时方法栈帧中的局部变量表可能也会指向于堆中的其他变量,倘若直接回收,未来可能会指向无访问权限的内存区域,导致出现内存安全问题。

    4、虚引用Phantom Reference

    Phantom ['fæntəm]

    adj.幽灵的;幻觉的;有名无实的

    通过名称我们就可以发现,这个引用的强度属于微乎其微的。事实也的确如此。

    我们几乎已经无法通过虚引用,查找到任何其所指向实例的内部信息。唯一可以获得的仅仅是该对象是否已经被回收(即是否已经经过一次GC过程)。如果非要用虚引用与现实生活中的某种联系相类比的话,个人觉得有点像已经丢弃到回收站中的文件,当我们打开回收站时,是不能直接使用这些软件的,只能判断这些软件有没有被回收掉,而如果真正想再次使用这些软件的话,需要再次建立关系性更强的引用才可以。

    虚引用与上述的其他三个引用有比较大的区别。

    我们先来看一段代码

    1 import java.lang.ref.PhantomReference;

    2 import java.lang.ref.ReferenceQueue;

    3

    4 public class PhantomReferenceLearn

    5 {

    6    public void init() throws InterruptedException

    7    {

    8        ReferenceQueue<PhantomReferenceLearn> Refqueue = new ReferenceQueue<PhantomReferenceLearn>();

    9        PhantomReference<PhantomReferenceLearn> pr = new PhantomReference<PhantomReferenceLearn>(

    10                new PhantomReferenceLearn(), Refqueue);

    11        System.gc();

    12        Thread.sleep(1000);

    13        boolean isClear = Refqueue.poll() == pr;

    14        System.out.println(isClear);

    15    }

    16 }

    与软引用和弱引用不同的是,虚引用的构造函数只能同时伴随着一个引用队列来构造。当虚引用回收时,引用会被加载到构造时绑定的引用队列中,可以通过出队的方式来查看引用是否已经被回收。ps.与虚引用类似,软引用和弱引用也有类似的用法,不同的地方是虚引用只能这样使用。(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )

    对于虚引用的使用还有以下几点要介绍

    1)、由于虚引用在定义时,就已经明确其不可以通过引用关系,取出指向内存中的数据,因此尽管虚引用实例中也存有get()方法,但实际上一定返回的是null值,这点是在JDK代码中写死的:

    注释的意思是返回一个对象的引用,但是由于虚引用是始终不可达的,因此始终返回null

    1    /**

    2     * Returns this reference object's referent. Because the referent of a

    3     * phantom reference is always inaccessible, this method always returns

    4     * <code>null</code>.

    5     *

    6     * @return <code>null</code>

    7     */

    8    public T get() {

    9        return null;

    10 }

    2)、虚引用的回收时机

    虚引用与弱引用类似,都是在GC阶段会被回收:

    不同之处是弱引用在GC阶段,一旦发现对象是弱引用,即被插入ReferenceQueue队列中,而虚引用是在对象被销毁后才会被放入ReferenceQueue队列中。

    3)、虚引用的必要性

    乍看起来觉得虚引用存在的必要性非常弱,他的目的就是判断GC是否已经开始了回收,这点功能其实上弱引用完全可以达到。但是恰恰是虚引用的不可达性,有时是必要的。

    如下面三种场景下:

    (1)在处理数据时,我们希望有些保密数据的引用是完全切断的,不可达的。但是我们又希望可以知道这些数据是否已经被回收掉,那么这时可以考虑虚引用。

    (2)在引用变量变量被赋予新值后,我们希望无论通过何种情况,旧有引用指向的变量的都不被重新建立强引用(可能是代码误操作,或者是攻击行为),这块内存的数据在下次GC期间会被永久的删除掉,这是也可以考虑虚引用。

    (3)根据引用对象被插入到引用队列的时机,我们希望知道对象在完全被销毁后的时间点。

    针对于第三个用途,很多人会疑惑,知道这个时间点,我们能有什么用呢?(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )这里先说一个题外话,搞过Java的人,基本都应该知道对象有一个和C++类似的回收方法:

    protected void java.lang.Object.finalize()

    这个方法需要各个需要调用的开发人员自己去复写。为的就是在对象析构的时刻做很多事情。但是对象从回收到调用析构方法被调用是一个非常复杂的过程(这个我以后会讲),所以在很多书中都介绍,不要去复写析构方法,如注明的《Effective Java》。但是很多时候我们万不得已,必须要在当前类被回收的时候做出一些行为。这时候就可以通过虚引用的形式,判断当前对象是否已经被加入到引用队列中,如果已经添加,那么做出相应的行为即可。

    这样基本上就把四种引用的含义和使用多介绍完了。

    最后的最后,很多人会认为引用的优先级如下:强引用>软引用>弱引用>虚引用。

    这里个人觉得没必要扯到优先级上,四种引用各有优劣,唯一的区别就是引用对象与内存之间的关系强度的大小。强度大的,可以让JVM不回或晚回收。强度弱的,即使对象还没有被回收,就无法通过引用获取到内存信息。

上一篇:java的调试/运行

下一篇:【昆明Java培训】Java线程池

最新开班日期  |  更多

Java--零基础全日制班

Java--零基础全日制班

开班日期:7月31日

Java--零基础业余班

Java--零基础业余班

开班日期:7月31日

Java--周末提升班

Java--周末提升班

开班日期:7月31日

Java--零基础周末班

Java--零基础周末班

开班日期:7月31日

  • 网址:http://km .java.tedu.cn      地址:昆明市官渡区春城路62号证券大厦附楼6楼
  • 课程培训电话:18487146383    全国服务监督电话:400-111-8989
  • 服务邮箱 tousu@tedu.cn
  • 2001-2016 达内时代科技集团有限公司 版权所有 京ICP证8000853号-56