目的的强

目的的强

 

1.对象的强、软、弱和虚引用

在JDK
1.2原先的本子中,若一个对象不被其他变量引用,那么程序就无法再利用这些目的。也就是说,唯有对象处于可触及(reachable)状态,程序才能运用它。从JDK
1.2版本开端,把目的的引用分为4种级别,从而使程序能更进一步灵活地决定目的的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。图1为指标应用类层次。

 

 

图1

⑴强引用(StrongReference)

强引用是应用最普遍的引用。假若一个目的具备强引用,这垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序卓殊终止,也不会靠随意回收具有强引用的目的来缓解内存不足的问题。

 

⑵软引用(SoftReference)

要是一个对象只持有软引用,则内存空间丰硕,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这么些目标的内存。只要垃圾回收器没有回收它,该对象就足以被先后采取。软引用可用来贯彻内存敏感的高速缓存(下文给出示例)。

软引用可以和一个引用队列(ReferenceQueue)联合使用,假诺软引用所引用的靶子被垃圾回收器回收,Java虚拟机就会把这么些软引用投入到与之提到的引用队列中。

 

⑶弱引用(WeakReference)

弱引用与软引用的区分在于:只拥有弱引用的目的具备更短命的生命周期。在垃圾堆回收器线程扫描它所管辖的内存区域的历程中,一旦发现了只持有弱引用的目的,不管当前内存空间充分与否,都会回收它的内存。然则,由于垃圾堆回收器是一个先行级很低的线程,因而不肯定会快速发现这多少个只持有弱引用的对象。

弱引用可以和一个引用队列(ReferenceQueue)联合使用,假设弱引用所引用的对象被垃圾回收,Java虚拟机就会把这些弱引用出席到与之提到的引用队列中。

 

⑷虚引用(PhantomReference)

“虚引用”顾名思义,就是形同虚设,与另外二种引用都不比,虚引用并不会操纵对象的生命周期。即使一个对象仅具有虚引用,那么它就和尚未其他引用一样,在其他时候都可能被垃圾回收器回收。

虚引用紧要用来跟踪对象被垃圾回收器回收的运动。虚引用与软引用和弱引用的一个分别在于:虚引用必须和引用队列(ReferenceQueue)联合利用。当废品回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存往日,把那么些虚引用投入到与之 关联的引用队列中。

ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);

先后可以经过判断引用队列中是否曾经进入了虚引用,来询问被引用的对象是否快要被垃圾回收。倘若程序意识某个虚引用已经被出席到引用队列,那么就足以在所引述的对象的内存被回收从前使用必要的行进。

2.对象可及性的判断

    在众多时候,一个目标并不是从根集直接引用的,而是一个目的被另外对象引用,甚至还要被多少个对象所引用,从而组合一个以根集为顶的树形结构。如图2所示

 

 

    在那个树形的引用链中,箭头的趋势代表了引用的趋向,所指向的目标是被引用对象。由图可以见到,从根集到一个目的足以由众多条途径。比如到达目的5的门道就有①-⑤,③-⑦两条路径。由此拉动了一个题材,这就是某个对象的可及性如何判定:

◆单条引用路径可及性判断:在这条路线中,最弱的一个引用决定对象的可及性。

◆多条引用路径可及性判断:几条路径中,最强的一条的引用决定对象的可及性。

    比如,我们假诺图2中引用①和③为强引用,⑤为软引用,⑦为弱引用,对于目的5遵照这五个判断标准,路径①-⑤取最弱的引用⑤,由此该路线对目的5的引用为软引用。同样,③-⑦为弱引用。在这两条路线之间取最强的引用,于是对象5是一个软可及对象。

3.运用软引用构建敏感数据的缓存

3.1 为什么需要利用软引用

   首先,我们看一个雇员信息查询系统的实例。我们将接纳一个Java语言实现的雇员信息查询系统查询存储在磁盘文件或者数据库中的雇员人事档案音讯。作为一个用户,我们全然有可能需要回头去查看几分钟甚至几分钟前查看过的雇员档案音信(同样,我们在浏览WEB页面的时候也时时会动用“后退”按钮)。这时大家平常会有两种程序实现格局:一种是把过去翻开过的雇员音讯保存在内存中,每一个囤积了雇员档案新闻的Java对象的生命周期贯穿整个应用程序始终;另一种是当用户起初翻看其他雇员的档案音信的时候,把囤积了最近所查看的雇员档案音信的Java对象结束引用,使得垃圾收集线程可以回收其所占有的内存空间,当用户再度索要浏览该雇员的档案音信的时候,重新构建该雇员的音信。很通晓,第一种实现格局将导致大气的内存浪费,而第二种实现的弱点在于就是垃圾收集线程还没有进展垃圾收集,包含雇员档案信息的对象依然完好无损地保存在内存中,应用程序也要重新构建一个对象。我们领会,访问磁盘文件、访问网络资源、查询数据库等操作都是熏陶应用程序执行性能的基本点元素,假使能再一次取得这些没有被回收的Java对象的引用,必将缩短不必要的拜会,大大提高程序的运转速度。

 

3.2 如果选拔软引用

SoftReference的风味是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在废品线程对这些Java对象回收前,SoftReference类所提供的get()方法重临Java对象的强引用。此外,一旦垃圾线程回收该Java对象之后,get()方法将回到null。

人事档案,看下边代码:

MyObject aRef = new  MyObject();
SoftReference aSoftRef=new SoftReference(aRef);

    此时,对于这一个MyObject对象,有两个引用路径,一个是来自SoftReference对象的软引用,一个出自变量aReference的强引用,所以这么些MyObject对象是强可及对象。

进而,我们得以停止aReference对这些MyObject实例的强引用:

aRef = null;

之后,这一个MyObject对象变成了软可及对象。尽管垃圾收集线程举办内存垃圾收集,并不会因为有一个SoftReference对该目标的引用而一直保留该对象。Java虚拟机的废物收集线程对软可及对象和其它一般Java对象进行了分别对待:软可及对象的清理是由垃圾收集线程遵照其特定算法遵照内存需求决定的。也就是说,垃圾收集线程会在虚拟机抛出OutOfMemoryError在此之前回收软可及对象,而且虚拟机会尽可能优先回收长日子闲置不用的软可及对象,对这些刚刚构建的或碰巧使用过的“新”软可反对象会被虚拟机尽可能保留。在回收那个目的此前,我们能够透过:

MyObject anotherRef=(MyObject)aSoftRef.get();

    重新取得对该实例的强引用。而回收之后,调用get()方法就只可以拿到null了。

 

3.3 使用ReferenceQueue清除失去了软引用对象的SoftReference

用作一个Java对象,SoftReference对象除了有着保存软引用的特殊性之外,也拥有Java对象的平时。所以,当软可及对象被回收之后,即便这些SoftReference对象的get()方法重回null,但那一个SoftReference对象已经不再持有存在的市值,需要一个适龄的破除机制,避免大量SoftReference对象带来的内存泄漏。在java.lang.ref包里还提供了ReferenceQueue。如若在开创SoftReference对象的时候,使用了一个ReferenceQueue对象作为参数提供给SoftReference的构造方法,如:

ReferenceQueue queue = new  ReferenceQueue();
SoftReference  ref=new  SoftReference(aMyObject, queue);

    那么当以此SoftReference所软引用的aMyOhject被垃圾收集器回收的同时,ref所强引用的SoftReference对象被列入ReferenceQueue。也就是说,ReferenceQueue中保存的目的是Reference对象,而且是早已错过了它所软引用的对象的Reference对象。此外从ReferenceQueue这一个名字也得以看到,它是一个队列,当我们调用它的poll()方法的时候,假如这一个行列中不是空队列,那么将回来队列后边的不得了Reference对象。

在其它时候,我们都得以调用ReferenceQueue的poll()方法来检查是不是有它所关心的非强可及对象被回收。假若队列为空,将赶回一个null,否则该情势重返队列中后面的一个Reference对象。利用那多少个主意,我们可以检查哪个SoftReference所软引用的目的已经被回收。于是我们可以把那一个失去所软引用的对象的SoftReference对象清除掉。常用的点子为:

SoftReference ref = null;
while ((ref = (EmployeeRef) q.poll()) != null) {
    // 清除ref
}

明亮了ReferenceQueue的做事机制之后,我们就足以最先协会一个Java对象的快速缓存器了。

 

3.4透过软可及对象重获方法实现Java对象的高速缓存

    利用Java2平台垃圾收集体制的表征以及前述的垃圾对象重获方法,大家经过一个雇员音讯查询系统的小例子来验证什么构建一种高效缓存器来避免双重构建同一个目的带来的属性损失。我们将一个雇员的档案音信定义为一个Employee类:

public class Employee {
    private String id;// 雇员的标识号码
    private String name;// 雇员姓名
    private String department;// 该雇员所在部门
    private String Phone;// 该雇员联系电话
    private int salary;// 该雇员薪资
    private String origin;// 该雇员信息的来源
 
    // 构造方法
    public Employee(String id) {
       this.id = id;
       getDataFromlnfoCenter();
    }
 
    // 到数据库中取得雇员信息
    private void getDataFromlnfoCenter() {
       // 和数据库建立连接井查询该雇员的信息,将查询结果赋值
       // 给name,department,plone,salary等变量
       // 同时将origin赋值为"From DataBase"
    }
……

其一Employee类的构造方法中我们能够预见,假设每趟需要查询一个雇员的音信。哪怕是几秒中在此以前刚刚查询过的,都要重新构建一个实例,这是急需耗费很多时间的。下面是一个对Employee对象开展缓存的缓存器的定义:

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Hashtable;
public class EmployeeCache {
    static private EmployeeCache cache;// 一个Cache实例
    private Hashtable<String,EmployeeRef> employeeRefs;// 用于Chche内容的存储
    private ReferenceQueue<Employee> q;// 垃圾Reference的队列
 
    // 继承SoftReference,使得每一个实例都具有可识别的标识。
    // 并且该标识与其在HashMap内的key相同。
    private class EmployeeRef extends SoftReference<Employee> {
       private String _key = "";
 
       public EmployeeRef(Employee em, ReferenceQueue<Employee> q) {
           super(em, q);
           _key = em.getID();
       }
    }
 
    // 构建一个缓存器实例
    private EmployeeCache() {
       employeeRefs = new Hashtable<String,EmployeeRef>();
       q = new ReferenceQueue<Employee>();
    }
 
    // 取得缓存器实例
    public static EmployeeCache getInstance() {
       if (cache == null) {
           cache = new EmployeeCache();
       }
       return cache;
    }
 
    // 以软引用的方式对一个Employee对象的实例进行引用并保存该引用
    private void cacheEmployee(Employee em) {
       cleanCache();// 清除垃圾引用
       EmployeeRef ref = new EmployeeRef(em, q);
       employeeRefs.put(em.getID(), ref);
    }
 
    // 依据所指定的ID号,重新获取相应Employee对象的实例
    public Employee getEmployee(String ID) {
       Employee em = null;
       // 缓存中是否有该Employee实例的软引用,如果有,从软引用中取得。
       if (employeeRefs.containsKey(ID)) {
           EmployeeRef ref = (EmployeeRef) employeeRefs.get(ID);
           em = (Employee) ref.get();
       }
       // 如果没有软引用,或者从软引用中得到的实例是null,重新构建一个实例,
       // 并保存对这个新建实例的软引用
       if (em == null) {
           em = new Employee(ID);
           System.out.println("Retrieve From EmployeeInfoCenter. ID=" + ID);
           this.cacheEmployee(em);
       }
       return em;
    }
 
    // 清除那些所软引用的Employee对象已经被回收的EmployeeRef对象
    private void cleanCache() {
       EmployeeRef ref = null;
       while ((ref = (EmployeeRef) q.poll()) != null) {
           employeeRefs.remove(ref._key);
       }
    }
 
    // 清除Cache内的全部内容
    public void clearCache() {
       cleanCache();
       employeeRefs.clear();
       System.gc();
       System.runFinalization();
    }
}

4.施用弱引用构建非敏感数据的缓存

4.1大局 Map 造成的内存泄漏

不知不觉对象保留最常见的来头是行使Map将元数据与暂时对象(transient
object)相关联。假定一个目的具备中等生命周期,比分配它的分外情势调用的生命周期长,可是比应用程序的生命周期短,如客户机的套接字连接。需要将部分元数据与那么些套接字关联,如生成连接的用户的标识。在创立Socket时是不知情那个消息的,并且不能够将数据增长到Socket对象上,因为不可能说了算 Socket 类或者它的子类。这时,典型的措施就是在一个大局 Map 中贮存这个音讯,如下边的SocketManager 类所示:使用一个大局
Map 将元数据涉嫌到一个目的。

public class SocketManager {
    private Map<Socket, User> m = new HashMap<Socket, User>();
 
    public void setUser(Socket s, User u) {
       m.put(s, u);
    }
 
    public User getUser(Socket s) {
       return m.get(s);
    }
 
    public void removeUser(Socket s) {
       m.remove(s);
    }
}

这种办法的题目是元数据的生命周期需要与套接字的生命周期挂钩,不过唯有准确地领会哪些时候程序不再需要那些套接字,并切记从 Map 中删除相应的映射,否则,Socket 和 User 对象将会永远留在 Map 中,远远超越响应了请求和倒闭套接字的光阴。这会堵住 Socket 和 User 对象被垃圾收集,尽管应用程序不会再利用它们。这一个目标留下来不受控制,很容易导致程序在长日子运作后内存爆满。除了最简易的情况,在几乎拥有境况下找出咋样时候 Socket 不再被先后行使是一件很可恶和易于失误的天职,需要人工对内存举办保管。

 

4.2怎么使用WeakHashMap

在Java集合中有一种独特的Map类型—WeakHashMap,在这种Map中存放了键对象的弱引用,当一个键对象被垃圾回收器回收时,那么相应的值对象的引用会从Map中删除。WeakHashMap可以节省存储空间,可用来缓存那多少个非必须存在的多少。关于Map接口的相似用法。

下边示例中MapCache类的main()方法创立了一个WeakHashMap对象,它存放了一组Key对象的弱引用,此外main()方法还成立了一个数组对象,它存放了有的Key对象的强引用。

import java.util.WeakHashMap;
 
class Element {
    private String ident;
 
    public Element(String id) {
       ident = id;
    }
 
    public String toString() {
       return ident;
    }
 
    public int hashCode() {
       return ident.hashCode();
    }
 
    public boolean equals(Object obj) {
       return obj instanceof Element && ident.equals(((Element) obj).ident);
    }
   
    protected void finalize(){
       System.out.println("Finalizing "+getClass().getSimpleName()+" "+ident);
    }
}
 
class Key extends Element{
    public Key(String id){
       super(id);
    }
}
 
class Value extends Element{
    public Value (String id){
       super(id);
    }
}
 
public class CanonicalMapping {
    public static void main(String[] args){
       int size=1000;
       Key[] keys=new Key[size];
       WeakHashMap<Key,Value> map=new WeakHashMap<Key,Value>();
       for(int i=0;i<size;i++){
           Key k=new Key(Integer.toString(i));
           Value v=new Value(Integer.toString(i));
           if(i%3==0)
              keys[i]=k;
           map.put(k, v);
       }
       System.gc();
    }
}

从打印结果可以见到,当执行System.gc()方法后,垃圾回收器只会回收那个单纯持有弱引用的Key对象。id可以被3整除的Key对象拥有强引用,因而不会被回收。

 

4.3用 WeakHashMap 堵住泄漏

在 SocketManager 中制止泄漏很容易,只要用 WeakHashMap 代替 HashMap 就行了。(这里假定SocketManager不需要线程安全)。当映射的生命周期必须与键的生命周期联系在一起时,可以使用这种情势。用WeakHashMap修复SocketManager。

public class SocketManager {
    private Map<Socket,User> m = new WeakHashMap<Socket,User>();
   
    public void setUser(Socket s, User u) {
        m.put(s, u);
    }
    public User getUser(Socket s) {
        return m.get(s);
    }
}

 

4.4相当使用引用队列

WeakHashMap 用弱引用承载映射键,这使得应用程序不再使用键对象时它们可以被垃圾收集,get() 实现可以遵照WeakReference.get() 是否重临 null 来区分死的映射和活的映射。可是这只是谨防 Map 的内存消耗在应用程序的生命周期中不断充实所需要做的干活的一半,还亟需做一些工作以便在键对象被采集后从 Map 中剔除死项。否则,Map 会充满对应于死键的项。即使这对于应用程序是不可见的,然而它依旧会造成应用程序耗尽内存。

引用队列是污物收集器向应用程序重返关于目的生命周期的音讯的重中之重格局。弱引用有个构造函数取引用队列作为参数。假使用关联的引用队列成立弱引用,在弱引用对象变成 GC 候选对象时,这些引用对象就在引用清除后出席到引用队列中(具体参考上文软引用示例)。

WeakHashMap 有一个名为 expungeStaleEntries() 的私有方法,大多数 Map 操作中会调用它,它去掉引用队列中装有失效的引用,并剔除关联的炫耀。

5.UML:使用关联类指明特定格局的引用

关联类可以用来指明特定格局的引用,如弱(weak)、软(soft)或虚 (phantom)引用。

 

 

也得以如下的构造型格局。

 

 

6.参考资料

[1]Thinking in Java4th

[2]孙卫琴,Java面向对象编程,电子工业出版社,2006

[3]Robert Martin,UML for java
programmers,2004

[4] 通过Java软可及对象的重获提升程序性能,张立明,陈朔鹰,程序员,2003,08

[5] Monica Pawlan,Reference Objects
and Garbage Collection,

[url]http://java.sun.com/developer/technicalArticles/ALT/RefObj/\[/url\]

[6]Brian
Goetz,Java 理论与实践: 用弱引用堵住内存泄漏,

[url]http://www-128.ibm.com/developerworks/cn/java/j-jtp11225/\[/url\]

正文出自 “子 孑
博客,请务必保留此出处http://zhangjunhd.blog.51cto.com/113473/53092

admin

网站地图xml地图