浅谈Java中的引用

在Java语言中,引用是指,某一个数据,表明的是另一块内存的的起始地址,那么咱们就称这个数据为引用。java

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

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

一、强引用 Strong Referenceui

这是Java程序中,最广泛的一种引用。this

程序建立一个对象,而且把这个对象赋值给一个引用变量,咱们就称这个引用变量为强引用。不少书上说,强引用是不会被GC回收掉的,我的以为这话是须要背景的:即强引用变量所处的位置,必定是在GC回收,所断定的Root节点可以依次传递到的引用,若是出现孤立的循环引用。那么即便对象中,存在强引用,也必定是会被回收掉的。其次须要强调的就是强引用不被回收,必定要(防盗链接:本文首发自http://www.cnblogs.com/jilodream/ )处在强引用所在的做用域中,如方法栈已经弹出,那么栈帧中的局部变量表中的变量就会被回收,其中存在的强引用的指向关系也会被解除。固然叨叨这么多,只是想说,强引用是否被回收,必定要看具体的状况,而不能一律而论。编码

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

二、软引用 Soft Reference设计

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

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

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

三、弱引用 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变量做为参数(这个之后会说)。同时方法栈帧中的局部变量表可能也会指向于堆中的其余变量,假若直接回收,将来可能会指向无访问权限的内存区域,致使出现内存安全问题。

四、虚引用 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不回或晚回收。强度弱的,即便对象尚未被回收,就没法经过引用获取到内存信息。

相关文章
相关标签/搜索