java 强引用弱引用

一、概述java

   在JDK1.2之前的版本中,当一个对象不被任何变量引用,那么程序就没法再使用这个对象。也就是说,只有对象处于可触及状态,程序才能使用它。这 就像在平常生活中,从商店购买了某样物品后,若是有用,就一直保留它,不然就把它扔到垃圾箱,由清洁工人收走。通常说来,若是物品已经被扔到垃圾箱,想再 把它捡回来使用就不可能了。
   但有时候状况并不这么简单,你可能会遇到相似鸡肋同样的物品,食之无味,弃之惋惜。这种物品如今已经无用了,保留它会占空间,可是马上扔掉它也不划算,因 为也许未来还会派用场。对于这样的无关紧要的物品,一种折衷的处理办法是:若是家里空间足够,就先把它保留在家里,若是家里空间不够,即便把家里全部的垃 圾清除,仍是没法容纳那些必不可少的生活用品,那么再扔掉这些无关紧要的物品。
   从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。算法

 

 

二、强引用
   平时咱们编程的时候例如:Object object=new Object();那object就是一个强引用了。若是一个对象具备强引用,那就相似于必不可少的生活用品,垃圾回收器毫不会回收它。当内存空 间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具备强引用的对象来解决内存不足问题。数据库

 

三、软引用(SoftReference)
   若是一个对象只具备软引用,那就相似于可有可物的生活用品。若是内存空间足够,垃圾回收器就不会回收它,若是内存空间不足了,就会回收这些对象的内存。只 要垃圾回收器没有回收它,该对象就能够被程序使用。软引用可用来实现内存敏感的高速缓存。 软引用能够和一个引用队列(ReferenceQueue)联 合使用,若是软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。编程


四、弱引用(WeakReference)   数组

   若是一个对象只具备弱引用,那就相似于可有可物的生活用品。弱引用与软引用的区别在于:只具备弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程当中,一旦发现了只具备弱引用的对象,无论当前内存空间足够与否,都会回收它的内存。不过,因为垃圾回收器是一个优先级很低的线程, 所以不必定会很快发现那些只具备弱引用的对象。  弱引用能够和一个引用队列(ReferenceQueue)联合使用,若是弱引用所引用的对象被垃圾回 收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。 缓存

 

 五、虚引用(PhantomReference)   安全

   "虚引用"顾名思义,就是形同虚设,与其余几种引用都不一样,虚引用并不会决定对象的生命周期。若是一个对象仅持有虚引用,那么它就和没有任何引用同样,在 任什么时候候均可能被垃圾回收。 虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,若是发现它还有虚引用,就会在回收对象的内存以前,把这个虚引用加入到与之 关联的引用队列中。程序能够经过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序若是发现某个虚引用已经被加入到引用队 列,那么就能够在所引用的对象的内存被回收以前采起必要的行动。 网络

 

六、相关应用app

  在java.lang.ref包中提供了三个类:SoftReference类、WeakReference类和PhantomReference类,它 们分别表明软引用、弱引用和虚引用。ReferenceQueue类表示引用队列,它能够和这三种引用类联合使用,以便跟踪Java虚拟机回收所引用的对 象的活动。ide

如下程序建立了一个String对象、ReferenceQueue对象和WeakReference对象:

 

以上程序代码执行完毕,内存中引用与对象的关系如图2所示



       图2 "hello"对象同时具备强引用和弱引用

 

     在图2中,带实线的箭头表示强引用,带虚线的箭头表示弱引用。从图中能够看出,此时"hello"对象被str强引用,而且被一个WeakReference对象弱引用,所以"hello"对象不会被垃圾回收。

 

   在如下程序代码中,把引用"hello"对象的str变量置为null,而后再经过WeakReference弱引用的get()方法得到"hello"对象的引用:

    执行完以上第④行后,内存中引用与对象的关系如图3所示,此 时"hello"对象仅仅具备弱引用,所以它有可能被垃圾回收。假如它尚未被垃圾回收,那么接下来在第⑤行执行wf.get()方法会返回 "hello"对象的引用,而且使得这个对象被str1强引用。再接下来在第⑥行执行rq.poll()方法会返回null,由于此时引用队列中没有任何 引用。ReferenceQueue的poll()方法用于返回队列中的引用,若是没有则返回null。



     图3 "hello"对象只具备弱引用

 

    在如下程序代码中,执行完第④行后,"hello"对象仅仅具备弱引用。接下来两次调用System.gc()方法,催促垃圾回收器工做,从而提升 "hello"对象被回收的可能性。假如"hello"对象被回收,那么WeakReference对象的引用被加入到ReferenceQueue中, 接下来wf.get()方法返回null,而且rq.poll()方法返回WeakReference对象的引用。图4显示了执行完第⑧行后内存中引用与 对象的关系。



 

  图4 "hello"对象被垃圾回收,弱引用被加入到引用队列

 

    在如下代码References类中,依次建立了10个软引用、10个弱引用和10个虚引用,它们各自引用一个Grocery对象。从程序运 行时的打印结果能够看出,虚引用形同虚设,它所引用的对象随时可能被垃圾回收,具备弱引用的对象拥有稍微长的生命周期,当垃圾回收器执行回收操做时,有可 能被垃圾回收,具备软引用的对象拥有较长的生命周期,但在Java虚拟机认为内存不足的状况下,也会被垃圾回收。

 

     在Java集合中有一种特殊的Map类型:WeakHashMap, 在这种Map中存放了键对象的弱引用,当一个键对象被垃圾回收,那么相应的值对象的引用会从Map中删除。WeakHashMap可以节约存储空间,可用来缓存那些非必须存在的数据。
     如下代码MapCache类的main()方法建立了一个WeakHashMap对象,它存放了一组Key对象的弱引用,此外main()方法还建立了一个数组对象,它存放了部分Key对象的强引用。

 

 

程序输出结果:

    从打印结果能够看出,当执行System.gc()方法后,垃圾回收器只会回收那些仅仅持有弱引用的Key对象。id能够被3整数的Key对象持有强引用,所以不会被回收。

 

七、使用软引用构建敏感数据的缓存

    7.1 为何须要使用软引用
    首先,咱们看一个雇员信息查询系统的实例。咱们将使用一个Java语言实现的雇员信息查询系统查询存储在磁盘文件或者数据库中的雇员人事档案信息。做为一 个用户,咱们彻底有可能须要回头去查看几分钟甚至几秒钟前查看过的雇员档案信息(一样,咱们在浏览WEB页面的时候也常常会使用“后退”按钮)。这时咱们 一般会有两种程序实现方式:一种是把过去查看过的雇员信息保存在内存中,每个存储了雇员档案信息的Java对象的生命周期贯穿整个应用程序始终;另外一种 是当用户开始查看其余雇员的档案信息的时候,把存储了当前所查看的雇员档案信息的Java对象结束引用,使得垃圾收集线程能够回收其所占用的内存空间,当 用户再次须要浏览该雇员的档案信息的时候,从新构建该雇员的信息。很显然,第一种实现方法将形成大量的内存浪费,而第二种实现的缺陷在于即便垃圾收集线程 尚未进行垃圾收集,包含雇员档案信息的对象仍然无缺地保存在内存中,应用程序也要从新构建一个对象。咱们知道,访问磁盘文件、访问网络资源、查询数据库 等操做都是影响应用程序执行性能的重要因素,若是能从新获取那些还没有被回收的Java对象的引用,必将减小没必要要的访问,大大提升程序的运行速度。


    7.2 若是使用软引用
    SoftReference的特色是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。也就是说, 一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的 get()方法返回Java对象的强引用。另外,一旦垃圾线程回收该Java对象以后,get()方法将返回null。

 看下面代码:

     此时,对于这个MyObject对象,有两个引用路径,一个是来自SoftReference对象的软引用,一个来自变量aReference的强引用,因此这个MyObject对象是强可及对象。
     随即,咱们能够结束aReference对这个MyObject实例的强引用:
     aRef = null; 
     此后,这个MyObject对象成为了软可及对象。若是垃圾收集线程进行内存垃圾收集,并不会由于有一个SoftReference对该对象的引用而始终 保留该对象。Java虚拟机的垃圾收集线程对软可及对象和其余通常Java对象进行了区别对待:软可及对象的清理是由垃圾收集线程根据其特定算法按照内存 需求决定的。也就是说,垃圾收集线程会在虚拟机抛出OutOfMemoryError以前回收软可及对象,并且虚拟机会尽量优先回收长时间闲置不用的软 可及对象,对那些刚刚构建的或刚刚使用过的“新”软可反对象会被虚拟机尽量保留。在回收这些对象以前,咱们能够经过:
     MyObject anotherRef=(MyObject)aSoftRef.get(); 
     从新得到对该实例的强引用。而回收以后,调用get()方法就只能获得null了。


     7.3 使用ReferenceQueue清除失去了软引用对象的SoftReference
     做为一个Java对象,SoftReference对象除了具备保存软引用的特殊性以外,也具备Java对象的通常性。因此,当软可及对象被回收以后, 虽然这个SoftReference对象的get()方法返回null,但这个SoftReference对象已经再也不具备存在的价值,须要一个适当的清 除机制,避免大量SoftReference对象带来的内存泄漏。在java.lang.ref包里还提供了ReferenceQueue。若是在建立 SoftReference对象的时候,使用了一个ReferenceQueue对象做为参数提供给SoftReference的构造方法,如:

    那么当这个SoftReference所软引用的aMyOhject被垃圾收集器回收的同时,ref所强引用的SoftReference对象被列入 ReferenceQueue。也就是说,ReferenceQueue中保存的对象是Reference对象,并且是已经失去了它所软引用的对象的 Reference对象。另外从ReferenceQueue这个名字也能够看出,它是一个队列,当咱们调用它的poll()方法的时候,若是这个队列中 不是空队列,那么将返回队列前面的那个Reference对象。
    在任什么时候候,咱们均可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强可及对象被回收。若是队列为空,将返回一个 null,不然该方法返回队列中前面的一个Reference对象。利用这个方法,咱们能够检查哪一个SoftReference所软引用的对象已经被回 收。因而咱们能够把这些失去所软引用的对象的SoftReference对象清除掉。经常使用的方式为:

     理解了ReferenceQueue的工做机制以后,咱们就能够开始构造一个Java对象的高速缓存器了。


     7.4经过软可及对象重获方法实现Java对象的高速缓存
 利用Java2平台垃圾收集机制的特性以及前述的垃圾对象重获方法,咱们经过一个雇员信息查询系统的小例子来讲明如何构建一种高速缓存器来避免重复构建同一个对象带来的性能损失。咱们将一个雇员的档案信息定义为一个Employee类:

 

    这个Employee类的构造方法中咱们能够预见,若是每次须要查询一个雇员的信息。哪怕是几秒中以前刚刚查询过的,都要从新构建一个实例,这是须要消耗不少时间的。下面是一个对Employee对象进行缓存的缓存器的定义:

 

java代码:

  1. import java.lang.ref.ReferenceQueue;      

  2. import java.lang.ref.SoftReference;      

  3. import java.util.Hashtable;      

  4. public class EmployeeCache {      

  5.     static private EmployeeCache cache;// 一个Cache实例      

  6.     private Hashtable employeeRefs;// 用于Cache内容的存储      

  7.     private ReferenceQueue q;// 垃圾Reference的队列      

  8.        

  9.     // 继承SoftReference,使得每个实例都具备可识别的标识。      

  10.     // 而且该标识与其在HashMap内的key相同。      

  11.     private class EmployeeRef extends SoftReference {      

  12.        private String _key = "";      

  13.        

  14.        public EmployeeRef(Employee em, ReferenceQueue q) {      

  15.            super(em, q);      

  16.            _key = em.getID();      

  17.        }      

  18.     }      

  19.        

  20.     // 构建一个缓存器实例      

  21.     private EmployeeCache() {      

  22.        employeeRefs = new Hashtable();      

  23.        q = new ReferenceQueue();      

  24.     }      

  25.        

  26.     // 取得缓存器实例      

  27.     public static EmployeeCache getInstance() {      

  28.        if (cache == null) {      

  29.            cache = new EmployeeCache();      

  30.        }      

  31.        return cache;      

  32.     }      

  33.        

  34.     // 以软引用的方式对一个Employee对象的实例进行引用并保存该引用      

  35.     private void cacheEmployee(Employee em) {      

  36.        cleanCache();// 清除垃圾引用      

  37.        EmployeeRef ref = new EmployeeRef(em, q);      

  38.        employeeRefs.put(em.getID(), ref);      

  39.     }      

  40.        

  41.     // 依据所指定的ID号,从新获取相应Employee对象的实例      

  42.     public Employee getEmployee(String ID) {      

  43.        Employee em = null;      

  44.        // 缓存中是否有该Employee实例的软引用,若是有,从软引用中取得。      

  45.        if (employeeRefs.containsKey(ID)) {      

  46.            EmployeeRef ref = (EmployeeRef) employeeRefs.get(ID);      

  47.            em = (Employee) ref.get();      

  48.        }      

  49.        // 若是没有软引用,或者从软引用中获得的实例是null,从新构建一个实例,      

  50.        // 并保存对这个新建实例的软引用      

  51.        if (em == null) {      

  52.            em = new Employee(ID);      

  53.            System.out.println("Retrieve From EmployeeInfoCenter. ID=" + ID);      

  54.            this.cacheEmployee(em);      

  55.        }      

  56.        return em;      

  57.     }      

  58.        

  59.     // 清除那些所软引用的Employee对象已经被回收的EmployeeRef对象      

  60.     private void cleanCache() {      

  61.        EmployeeRef ref = null;      

  62.        while ((ref = (EmployeeRef) q.poll()) != null) {      

  63.            employeeRefs.remove(ref._key);      

  64.        }      

  65.     }      

  66.        

  67.     // 清除Cache内的所有内容      

  68.     public void clearCache() {      

  69.        cleanCache();      

  70.        employeeRefs.clear();      

  71.        System.gc();      

  72.        System.runFinalization();      

  73.     }      

  74. }      

 

 

 8.使用弱引用构建非敏感数据的缓存
     8.1全局 Map 形成的内存泄漏
     无心识对象保留最多见的缘由是使用Map将元数据与临时对象(transient object)相关联。假定一个对象具备中等生命周期,比分配它的那个方法调用的生命周期长,可是比应用程序的生命周期短,如客户机的套接字链接。须要将 一些元数据与这个套接字关联,如生成链接的用户的标识。在建立Socket时是不知道这些信息的,而且不能将数据添加到Socket对象上,由于不能控制 Socket 类或者它的子类。这时,典型的方法就是在一个全局 Map 中存储这些信息,以下面的 SocketManager 类所示:使用一个全局 Map 将元数据关联到一个对象。

    这种方法的问题是元数据的生命周期须要与套接字的生命周期挂钩,可是除非准确地知道何时程序再也不须要这个套接字,并记住从 Map 中删除相应的映射,不然,Socket 和 User 对象将会永远留在 Map 中,远远超过响应了请求和关闭套接字的时间。这会阻止 Socket 和 User 对象被垃圾收集,即便应用程序不会再使用它们。这些对象留下来不受控制,很容易形成程序在长时间运行后内存爆满。除了最简单的状况,在几乎全部状况下找出 何时 Socket 再也不被程序使用是一件很烦人和容易出错的任务,须要人工对内存进行管理。


     8.2如何使用WeakHashMap
     在Java集合中有一种特殊的Map类型—WeakHashMap,在这种Map中存放了键对象的弱引用,当一个键对象被垃圾回收器回收时,那么相应的值 对象的引用会从Map中删除。WeakHashMap可以节约存储空间,可用来缓存那些非必须存在的数据。关于Map接口的通常用法。
    下面示例中MapCache类的main()方法建立了一个WeakHashMap对象,它存放了一组Key对象的弱引用,此外main()方法还建立了一个数组对象,它存放了部分Key对象的强引用。

 

java代码:

  1. import java.util.WeakHashMap;      

  2.        

  3. class Element {      

  4.     private String ident;      

  5.        

  6.     public Element(String id) {      

  7.        ident = id;      

  8.     }      

  9.        

  10.     public String toString() {      

  11.        return ident;      

  12.     }      

  13.        

  14.     public int hashCode() {      

  15.        return ident.hashCode();      

  16.     }      

  17.        

  18.     public boolean equals(Object obj) {      

  19.        return obj instanceof Element && ident.equals(((Element) obj).ident);      

  20.     }      

  21.           

  22.     protected void finalize(){      

  23.        System.out.println("Finalizing "+getClass().getSimpleName()+" "+ident);      

  24.     }      

  25. }      

  26.        

  27. class Key extends Element{      

  28.     public Key(String id){      

  29.        super(id);      

  30.     }      

  31. }      

  32.        

  33. class Value extends Element{      

  34.     public Value (String id){      

  35.        super(id);      

  36.     }      

  37. }      

  38.        

  39. public class CanonicalMapping {      

  40.     public static void main(String[] args){      

  41.        int size=1000;      

  42.        Key[] keys=new Key[size];      

  43.        WeakHashMap map=new WeakHashMap();      

  44.        for(int i=0;i< SPAN>    

  45.            Key k=new Key(Integer.toString(i));      

  46.            Value v=new Value(Integer.toString(i));      

  47.            if(i%3==0)      

  48.               keys[i]=k;      

  49.            map.put(k, v);      

  50.        }      

  51.        System.gc();      

  52.     }      

  53. }      

 

 

     从打印结果能够看出,当执行System.gc()方法后,垃圾回收器只会回收那些仅仅持有弱引用的Key对象。id能够被3整除的Key对象持有强引用,所以不会被回收。


    8.3用 WeakHashMap 堵住泄漏
    在 SocketManager 中防止泄漏很容易,只要用 WeakHashMap 代替 HashMap 就好了。(这里假定SocketManager不须要线程安全)。当映射的生命周期必须与键的生命周期联系在一块儿时,可使用这种方法。用 WeakHashMap修复 SocketManager。

 

     8.4配合使用引用队列     WeakHashMap 用弱引用承载映射键,这使得应用程序再也不使用键对象时它们能够被垃圾收集,get() 实现能够根据 WeakReference.get() 是否返回 null 来区分死的映射和活的映射。可是这只是防止 Map 的内存消耗在应用程序的生命周期中不断增长所须要作的工做的一半,还须要作一些工做以便在键对象被收集后从 Map 中删除死项。不然,Map 会充满对应于死键的项。虽然这对于应用程序是不可见的,可是它仍然会形成应用程序耗尽内存。  引用队列是垃圾收集器向应用程序返回关于对象生命周期的信息的主要方法。弱引用有个构造函数取引用队列做为参数。若是用关联的引用队列建立弱引用,在弱引用对象成为 GC 候选对象时,这个引用对象就在引用清除后加入到引用队列中(具体参考上文软引用示例)。     WeakHashMap 有一个名为 expungeStaleEntries() 的私有方法,大多数 Map 操做中会调用它,它去掉引用队列中全部失效的引用,并删除关联的映射。

相关文章
相关标签/搜索