【Java】弱引用的相关使用

Java的四种引用

1.强引用

最常见的引用类型,即使内存不够也不会回收。

思考:obj1这个强引用自身是否也是对象?占用内存吗?

public class StrongReferenceDemo {
    public static void main(String[] args) {
        //obj1是一个强引用,指向new出来的实际对象
        Object obj1 = new Object();
        Object obj2 = obj1;
        obj1 = null;
        System.gc();
        System.out.println(obj2);
    }
}

输出结果:java.lang.Object@4d7e1886

2.软引用

内存不够了就回收仅有软引用指向的对象,内存够的时候不用管。

package com.shin.demo05;

import java.lang.ref.SoftReference;

public class SoftReferenceDemo {
    public static void memoryEnough(){
        Object obj1 = new Object();
        //创建一个软引用,指向强引用obj所指的对象。
        SoftReference<Object> softReference = new SoftReference<Object>(obj1);
        System.out.println(obj1);
        System.out.println(softReference.get());
        obj1 = null;
        System.gc();
        System.out.println(obj1);
        System.out.println(softReference.get());
    }
    //-Xms5m -Xmx5m 故意制造OOM
    public static void memoryNotEnough(){
        Object obj1 = new Object();
        SoftReference<Object> softReference = new SoftReference<Object>(obj1);
        System.out.println(obj1);
        System.out.println(softReference.get());
        //此时只剩软引用
        obj1 = null;
        try{
            byte[] bytes = new byte[300*1024*1024];
        }catch (Throwable e){
            e.printStackTrace();
        }finally {
            System.out.println(obj1);
            System.out.println(softReference.get());
        }
    }
    public static void main(String[] args) {
        memoryEnough();
        System.out.println("=========================");
        memoryNotEnough();
    }
}

故意制造OOM,JVM参数-Xms5m -Xmx5m。自动GC的时候把软引用指向的对象回收了。使用softReference.get()可以获得指向的对象。

输出结果:

java.lang.Object@4d7e1886
java.lang.Object@4d7e1886
null
java.lang.Object@4d7e1886
=============
java.lang.Object@3cd1a2f1
java.lang.Object@3cd1a2f1
java.lang.OutOfMemoryError: Java heap space
	at com.shin.demo05.SoftReferenceDemo.memoryNotEnough(SoftReferenceDemo.java:25)
	at com.shin.demo05.SoftReferenceDemo.main(SoftReferenceDemo.java:36)
null
null

3.弱引用

仅被弱引用指向的对象在GC的时候一定会被回收,无论内存是否足够。

import java.lang.ref.WeakReference;

public class WeakReferenceDemo {
    public static void memoryEnough(){
        Object obj1 = new Object();
        WeakReference<Object> weakReference = new WeakReference<Object>(obj1);
        System.out.println(obj1);
        System.out.println(weakReference.get());
        obj1 = null;
        System.gc();
        System.out.println(obj1);
        System.out.println(weakReference);
        System.out.println(weakReference.get());
    }
    public static void main(String[] args) {
        memoryEnough();
    }
}

同样是使用weakReference.get()获取弱引用指向的对象。weakReference本身也要占用内存,可以理解为引用类型的对象。

输出结果:

java.lang.Object@4d7e1886
java.lang.Object@4d7e1886
null
java.lang.ref.WeakReference@3cd1a2f1
null

4.虚引用

仅被虚引用指向的对象也是只要GC就会回收,此外不可以通过虚引用get到指向的对象。

但是可以将虚引用回收的时候入引用队列ReferenceQueue。

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

public class PhantomReferenceDemo {
    public static void main(String[] args) {
        Object obj1 = new Object();
        ReferenceQueue queue = new ReferenceQueue();
        PhantomReference<Object> phantomReference = new PhantomReference<Object>(obj1,queue);

        System.out.println(obj1);
        System.out.println(phantomReference.get());
        System.out.println(queue.poll());
        System.out.println("===========================");
        obj1 = null;
        System.gc();
        System.out.println(obj1);
        System.out.println(phantomReference.get());
        System.out.println(queue.poll());
    }

}

输出结果:

java.lang.Object@4d7e1886
null
null
===========================
null
null
java.lang.ref.PhantomReference@3cd1a2f1

5.引用队列

软弱虚引用都可以和引用队列配合,在被回收的时候进入引用队列。注意入队的是引用,不是引用指向的对象。

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

public class ReferenceQueueDemo {
    public static void main(String[] args) {
        Object obj1 = new Object();
        ReferenceQueue queue = new ReferenceQueue();

        WeakReference<Object> weakReference = new WeakReference<Object>(obj1,queue);

        System.out.println(obj1);
        System.out.println(weakReference.get());
        System.out.println(queue.poll());
        System.out.println("===========================");
        //只剩下weak-reference,实际对象会被GC回收
        obj1 = null;
        System.gc();
        System.out.println(obj1);
        System.out.println(weakReference.get());
        //回收之后入队
        System.out.println(queue.poll());
    }
}

输出结果:

java.lang.Object@4d7e1886
java.lang.Object@4d7e1886
null
===========================
null
null
java.lang.ref.WeakReference@3cd1a2f1

弱引用的使用

WeakHashMap的使用

WeakHashMap实现原理和HashMap差不多,区别在于WeakHashMap的key是一个弱引用。

public class WeakHashMapDemo {
    public static void notWeak(){
        HashMap<Integer, String> map = new HashMap<>();
        Integer key = new Integer(1);
        String value = "HashMap";
        map.put(key,value);
        System.out.println(map);
        key = null;
        //显然还在,想要删除的话需要使用remove()方法
        System.out.println(map);
        System.gc();
        //显然还在,HashMap内部都是强引用
        System.out.println(map);
    }
    public static void weak(){
        WeakHashMap<Integer, String> map = new WeakHashMap<>();
        Integer key = new Integer(1);
        String value = "WeakHashMap";
        map.put(key,value);
        System.out.println(map);
        key = null;
        //不GC的话还在
        System.out.println(map);
        System.gc();
        //WeakHashMap的key是一个弱引用,GC之后key原来指向的对象被回收了
        //但是这里看到的是整个键值对都没了,其实用到了引用队列
        System.out.println(map);
    }

    public static void main(String[] args) {
        notWeak();
        weak();
    }

输出结果:

{1=HashMap}
{1=HashMap}
{1=HashMap}
{1=WeakHashMap}
{1=WeakHashMap}
{}

下面一段话摘自《Java核心技术卷1》:

设计 WeakHashMap类是为了解决一个有趣的问题。如果有一个值,对应的键已经不再使用了,将会出现什么情况呢? 假定对某个键的最后一次引用已经消亡,不再有任何途径引用这个值的对象了。但是,由于在程序中的任何部分没有再出现这个键,所以,这个键值对无法从映射中删除。为什么垃圾回收器不能够删除它呢? 难道删除无用的对象不是垃圾回收器的工作吗?

遗憾的是,事情没有这样简单。垃圾回收器跟踪活动的对象。只要映射对象是active的,其中的所有桶也是active的,它们不能被回收。因此,需要由程序负责从长期存活的映射表中删除那些无用的值。 或者使用 WeakHashMap完成这件事情。当对键的唯一引用来自散列条目时, 这一数据结构将与垃圾回收器协同工作一起删除键 / 值对。

下面是这种机制的内部运行情况。WeakHashMap使用弱引用(weak references) 保存键。WeakReference对象将引用保存到另外一个对象中,在这里,就是散列键。对于这种类型的对象,垃圾回收器用一种特有的方式进行处理。通常,如果垃圾回收器发现某个特定的对象已经没有他人引用了,就将其回收。然而,如果某个对象只能由 WeakReference引用,垃圾回收器仍然回收它,但要将引用这个对象的弱引用放人队列中。WeakHashMap将周期性地检查队列,以便找出新添加的弱引用。一个弱引用进人队列意味着这个键不再被他人使用,并且已经被收集起来。于是, WeakHashMap将删除对应的条目。

具体看WeakHashMap源码中的expungeStaleEntries()方法。

弱引用的子类MyEntry

如果一个类继承了WeakReference会怎样?

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

//MyEntry继承了WeakReference,那么MyEntry本身就是一个弱引用
//通过父类的构造方法可以指定referent和queue。
public class MyEntry extends WeakReference {

    //MyEntry是一个弱引用,自身还携带了一个属性value
    private Object value;
    //调用这个构造函数会返回一个MyEntry类型的弱引用
    //指向的是key所指的实际对象,引用队列是queue
    public MyEntry(Object key, Object value, ReferenceQueue queue) {
        //调用父类构造函数
        super(key,queue);
        this.value = value;
    }

    public static void main(String[] args) {
        //1、key是一个强引用
        Object key = new Object();
        Object value = new Object();
        ReferenceQueue<Object> queue = new ReferenceQueue<>();
        //2、创建了弱引用myEntry,指向强引用key所指的对象
        //   当只剩myEntry的时候,GC回收指向的实际对象,然后把myEntry放进queue中
        //   注意这里是把谁入队。引用队列入队的一定是引用,而不是引用指向的实际对象。
        MyEntry myEntry = new MyEntry(key, value, queue);
        System.out.println("key:        "+key);
        System.out.println("value:      "+value);
        System.out.println("myEntry指向: "+myEntry.get());
        System.out.println("queue:      "+queue.poll());
        System.out.println("===============================");
        key = null;
        System.gc();
        System.out.println("key:        "+key);
        System.out.println("value:      "+value);
        System.out.println("myEntry指向: "+myEntry.get());
        System.out.println("queue:      "+queue.poll());
    }
}

输出结果:

key:        java.lang.Object@4d7e1886
value:      java.lang.Object@3cd1a2f1
myEntry指向: java.lang.Object@4d7e1886
queue:      null
===============================
key:        null
value:      java.lang.Object@3cd1a2f1
myEntry指向: null
queue:      com.shin.demo05.MyEntry@2f0e140b

可以看到myEntry指向的是原本key所指的对象,GC之后被回收了,并且myEntry进入引用队列。

上一篇:Collection接口的常用方法


下一篇:JS 深浅拷贝