Java的四种引用详解与使用案例

Java有四种引用:强引用、软引用、弱引用和虚引用

整体结构

java.lang.ref包下

  • 强引用:Reference

  • 软引用:SoftReference

  • 弱引用:WeakReference

  • 虚引用:PhantomReference

  • 引用队列:ReferenceQueue

强引用

当内存不足时,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收。

强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器就不会碰这种对象。在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,不可能被垃圾回收机制回收,即使该对象以后永远都不会被用到JVM也不会回收。因此强引用是造成Java内存泄漏的主原因之一。

对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为null,就是可以被垃圾收集的了。

Student s = new Student

软引用

内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。

软引用作用是实现内存敏感的高速缓存。软引用可以和一个引用队列联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机会把软应用加入到与之关联的引用队列中。

package ref;

import java.lang.ref.SoftReference;

public class SoftReferenceDemo {

    /**
     * 内存够用就保留,不够就回收
     */
    public static void softRef_Memory_Enough() {
        Object o1 = new Object();
        SoftReference<Object> softReference = new SoftReference<>(o1);
        System.out.println(o1); //java.lang.Object@14ae5a5
        System.out.println(softReference.get()); //java.lang.Object@14ae5a5

        o1 = null;
        System.gc();

        System.out.println(o1); //null
        System.out.println(softReference.get()); //java.lang.Object@14ae5a5
    }

    /**
     * 生成大对象,设置小内存,自动GC
     * -Xms5m -Xmx5m -XX:+PrintGCDetails
     */
    public static void softRef_Memory_NotEnough() {
        Object o1 = new Object();
        SoftReference<Object> softReference = new SoftReference<>(o1);
        System.out.println(o1);
        System.out.println(softReference.get());

        o1 = null;

        try {
            // 创建大对象
            byte[] bytes = new byte[30 * 1024 * 1024];
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println(o1); //null
            System.out.println(softReference.get()); //null
        }
    }

    public static void main(String[] args) {
        softRef_Memory_Enough();
        softRef_Memory_NotEnough();
    }
}

软引用队列案例

作为一个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的poll()方法来检查是否有它所关心的非强引用对象被回收,再把这些失去所软引用的对象的SoftReference对象清除掉。

例如:

//继承SoftReference,实现对对象的软引用  
//这个类所引用的目标对象会在JVM内存不足时自动回收  
private class WeakEmployee extends WeakReference<Employee> {  
  
    private String key;  
          
    public String getKey() {  
        return key;  
    }  
  
    public WeakEmployee(Employee referent, ReferenceQueue<Employee> queue) {  
        super(referent, queue);  
        this.key = referent.getId();  
    }     
} 

private void cleanCache() {  
    WeakEmployee se = null;  
    while((se = (WeakEmployee)queue.poll()) != null) { 
        referent.remove(se.getKey());  
        System.out.println("对象ID : " + se.getKey() + "已经被JVM回收");  
    }  
} 

参考:https://www.cnblogs.com/daxin/archive/2013/06/03/3114679.html

弱引用

一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。很软引用类似,只是回收的更快。weak引用对象常常用于Map数据结构中,引用占用的内存空间较大的对象。

import java.lang.ref.WeakReference;

public class WeakReferenceDemo {
    public static void main(String[] args) {
        Object o1 = new Object();
        WeakReference<Object> weakReference = new WeakReference<>(o1);
        System.out.println(o1); //java.lang.Object@14ae5a5
        System.out.println(weakReference.get()); //java.lang.Object@14ae5a5

        o1 = null;
        System.gc();

        System.out.println(o1); //null
        System.out.println(weakReference.get()); //null
    }
}

应用场景

假如有一个应用需要读取大量的本地图片:

  • 如果每次读取图片都从硬盘读取则会影响性能;
  • 如果一次性全部加载到内存中又可能造成内存溢出。

此时可以使用软引用或弱引用解决。

设计思路为:用一个HashMap保存图片路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而避免OOM问题。

Map<String,SoftReference<Bitmap>> imageCache = new HashMap<>();

WeakHashMap

WeakHashMap的键是"弱键"。在WeakHashMap中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。

public class TestWeakHashMap {
 
    public static void main(String[] args) {
        WeakHashMap<String, String> weakHashMap = new WeakHashMap<>(10);
 
        String key0 = new String("kuang");
        String key1 = new String("zhong");
        String key2 = new String("wen");
 
        // 存放元素
        weakHashMap.put(key0, "q1");
        weakHashMap.put(key1, "q2");
        weakHashMap.put(key2, "q3");
        System.out.printf("weakHashMap: %s\n", weakHashMap);
 
        // 是否包含某key
        System.out.printf("contains key kuang : %s\n", weakHashMap.containsKey(key0));
        System.out.printf("contains key zhong : %s\n", weakHashMap.containsKey(key1));
 
        // 是否包含某value
        System.out.printf("contains value 0 : %s\n", weakHashMap.containsValue(0));
 
        // 移除key
        weakHashMap.remove(key2);
        System.out.printf("weakHashMap after remove: %s", weakHashMap);
 
        // 这意味着"弱键"key0再没有被其它对象引用,调用gc时会回收WeakHashMap中与key0对应的键值对
        key0 = null;
        // 内存回收,这里会回收WeakHashMap中与"key0"对应的键值对
        System.gc();
 
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
        // 遍历WeakHashMap
        Iterator iter = weakHashMap.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry en = (Map.Entry) iter.next();
            System.out.printf("next : %s - %s\n", en.getKey(), en.getValue());
        }
        // 打印WeakHashMap的实际大小
        System.out.printf("after gc WeakHashMap size: %s\n", weakHashMap.size());
    }
}

依次输出
weakHashMap: {wen=q3, zhong=q2, kuang=q1}
contains key kuang : true
contains key zhong : true
contains value 0 : false
weakHashMap after remove: {zhong=q2, kuang=q1}
next : zhong - q2
after gc WeakHashMap size: 1

参考:https://blog.csdn.net/u014294681/article/details/86522487

虚引用

虚引用不会决定对象的生命周期,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象。

当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

import java.lang.ref.ReferenceQueue;
import java.lang.ref.PhantomReference;

public class PhantomReferenceDemo {
    public static void main(String[] args) throws InterruptedException {
        Object o1 = new Object();
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
        PhantomReference<Object> phantomReference = new PhantomReference<>(o1,referenceQueue);

        System.out.println(o1);
        System.out.println(phantomReference.get());
        System.out.println(referenceQueue.poll());

        o1 = null;
        System.gc();
        Thread.sleep(500);

        System.out.println(o1);
        System.out.println(phantomReference.get());
        System.out.println(referenceQueue.poll());
    }
}

输出
java.lang.Object@14ae5a5
null
null
null
null
java.lang.ref.PhantomReference@7f31245a

猜你喜欢

转载自blog.csdn.net/TJtulong/article/details/104879688
今日推荐