关于Java中的四种引用类型

128 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

强引用

强引用是在开发中用的比较多的一种引用类型,比如我们对一个类的变量申明及定义

Object object = new Object()

这种引用类类型,只要 object 变量是一直指向 Object 对象的,那么 Object 这个对象就会一直存在于内存中,即使内存耗尽,Java的内存回收机制触发,也不会将 Object 这个对象进行回收掉

现在用一个代码实例来看看强引用

我们有一个类,这个类里有一个数组,数组的大小是 1024 * 1024(1MB) ,因此这个类占用的内存空间大概是 1MB 的样子

public class RefreceModel { 
    private int[] arry = new int[1024 * 1024];
}

我们来循环创建这个对象,并把这个对象存放到List集合里,因此List集合的底层对这个对象的引用是一个强引用

public static void main(String[] args) { 
 List<RefreceModel> refreceModelList = new ArrayList<>(); 
 for (int i = 0 ; i < 100 ; i++){ 
   System.out.println("============"+i); 
   refreceModelList.add(new RefreceModel());
   } 
}

我们再把JVM的堆内存空间设置为 20MB ,因为强引用的对象在堆内存中是不会被JVM回收掉的,所以这段程序在循环的过程中会直接报内存溢出,为了能更好的展示效果,再把堆内存通过参数设置把调小为 20MB ( -Xms20m -Xmx20m)

image.png 可以看到在循环到第五次的时候就报了内存溢出的错误

这就是强引用,只要引用变量没有被回收,那么这个变量所指向的对象就会一直在推内存空间中,如果不及时处理那么最终就会导致内存溢出

软引用

软引用的引用力度要弱于强引用,不管对应的引用变量有没有指向这个对象,只要Java的内存回收机制被触发了并且内存快要溢出的时候都会将这个对象回收掉,以此在释放更多的内存空间

软引用主要用到 SoftReference 这个类,也就是说只要对象被这个类所引用了,那么这个对象就是个软引用所指向的对象,那么就会被JVM回收掉

我们在上面的那个强引用例子的基础上稍微改下就可以是个软引用了

public static void main(String[] args) {
     List<SoftReference<RefreceModel>> refreceModelList = new ArrayList<>(); 
     for (int i = 0 ; i < 100 ; i++){ 
      System.out.println("============"+i); 
      refreceModelList.add(new SoftReference(new RefreceModel()));      
      System.out.println(refreceModelList.size());
     } 
 }

image.png

可以看到都已经循环完了都不会报内存溢出的错误

这就是因为内存快要溢出的时候,软引用在触发回收机制之后,会将软引用所指向的对象回收掉,这在一定程度上避免了内存溢出的情况

弱引用

弱引用的引用强度比软引用更弱,其特性跟软引用差不多,但也有不同的,如果对象被弱引用对象所引用,那么这个对象在GC的时候,不管内存是不是快要溢出都会直接被回收掉

弱引用主要用到 WeakReference 这个类,只要对象被这个类所引用,那么他就是一个弱引用

同样我们只要把上面的例子改下就是弱引用了

public static void main(String[] args) {
      List<WeakReference<RefreceModel>> refreceModelList = new ArrayList<>(); 
      for (int i = 0 ; i < 100 ; i++){ 
          System.out.println("============"+i); 
          refreceModelList.add(new WeakReference(new RefreceModel()));
      } 
}

其运行结果同样也是不会报内存溢出的情况

虚引用

虚引用是一种极弱的引用关系,定义完成之后,无法通过该引用获取指向的对象,为一个对象设置虚引用的唯一目的就是希望能在这个对象被回收时收到一个系统通知

虚引用主要用到 PhantomReference 这个类,但是虚引用需要与引用队列联合使用,当垃圾回收时,如果发现存在虚引用,就会在回收对象内存前,把这个虚引用加入与之关联的引用队列中

我们可以看看这个类的构造方法

image.png

ThreadLocal对于弱引用的应用

ThreadLocal一般在开发中很少去用到,这个类主要就是用来多线程之间共享变量的,但是线程都会复制这个变量对应副本,线程对变量的修改是互不干扰的

在Java中,线程对应一个Thread对象,在这个Thread对象的内部有一个 ThreadLocalMap 内部类,线程就是把变量的副本存放到这个类里面

public static void main(String[] args) { 
      ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(); 
      threadLocal.set(1); Integer integer = threadLocal.get(); 
      System.out.println(integer);
 }

在上面的代码中我们通过 ThreadLocal 的 set 方法设置了一个值,当我们通过 get 方法去获取值的时候,首先会从 ThreadLocalMap 获取,如果没有的话,就会直接把我们这个是设置的值存放到当前线程的 ThreadLocalMap 中去

我们可以看下这个get()方法的源码

public T get() {
    Thread t = Thread.currentThread();
    // 获取当前线程的 ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 获取对应的值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 如果 ThreadLocalMap 为null ,就调用 setInitialValue 方法初始化并将值设置到进去
    return setInitialValue();
}

TheadLocal对于弱引用的应用主要就在这个 ThreadLocalMap 中,我们可以来看看这个类

image.png

可以看到这个 ThreadLocalMap 类里的 Entry 是继承了 WeakReference 对象,这个对象就是弱引用的对象

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        // 把 key 交给 WeakReference 类来引用
        super(k);
        value = v;
    }
}

这个 Entry 所对应的 key 直接交给 WeakReference 类来引用,因此这个 ThreadLocalMap 类所对应的 key 就是个弱引用

而这个 key 传的又是 ThreadLocal 这个类,所以只要 ThreadLocal 这个对象所对应强引用变量置为null,那么这个 key 在GC的时候就会被回收掉,而 ThreadLocal 在使用 get() 和 set() 时,又会将那些 key 为null 的 value 置为 null,使 value 能够被垃圾回收,避免内存溢出