一、引用架构图

image.png

二、强引用(不回收)

当内存不足,JVM开始垃圾回收,对应强引用的对象,就算是出现了OOM也不会对该对象进行回收。
强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象,在JAVA中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会用到,JVM也不会回收。因此强引用是造成java内存泄漏的主要原因之一。
对于一个普通对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为null,一般认为就是可以被垃圾收集的了(具体需要看垃圾收集策略).

2.1 代码演示

  1. public class StrongReferenceDemo {
  2. public static void main(String[] args) {
  3. Object obj1 = new Object();//这样定义的默认就是强引用
  4. Object obj2 = obj1;//引用赋值
  5. obj1 = null;
  6. System.gc();//启动GC
  7. System.out.println(obj2);//obj2是强引用不会被垃圾回收
  8. }
  9. }

image.png

三、软引用SoftRerence(内存不够才回收)

软引用是一种相对强引用弱化了一些的引用,要求用java.lang.ref.SoftRerence类来实现,可以让对象豁免一些垃圾收集。
对于只有软引用的对象来说:

  • 当系统内存充足时它不会被收回
  • 当系统内存不足时它会被收回

软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收

3.1 代码演示

3.1.1 内存够用

  1. import java.lang.ref.SoftReference;
  2. public class SoftReferenceDemo {
  3. //内存够用的时候就保留,不够用就回收.
  4. public static void softRefMemoryEnough(){
  5. Object o1 = new Object();
  6. SoftReference<Object> softReference = new SoftReference<>(o1);
  7. System.out.println(o1);
  8. System.out.println(softReference.get());
  9. o1 = null;
  10. System.gc();//手动启动GC
  11. System.out.println(o1);
  12. System.out.println(softReference.get());
  13. }
  14. public static void main(String[] args) {
  15. softRefMemoryEnough();
  16. }
  17. }

image.png

3.1.2 内存不够用

  1. import java.lang.ref.SoftReference;
  2. public class SoftReferenceDemo {
  3. /**
  4. * JVM配置,故意产生对象并配置小的内存,让它内存不够用了导致OOM,看软引用的回收情况
  5. * -Xms5m -Xmx5m -XX:+PrintGCDetails
  6. */
  7. public static void softRefMemoryNotEnough(){
  8. Object o1 = new Object();
  9. SoftReference<Object> softReference = new SoftReference<>(o1);
  10. System.out.println(o1);
  11. System.out.println(softReference.get());
  12. o1 = null;
  13. try {
  14. //占用内存new个大对象
  15. byte[] bytes = new byte[30 * 1024 * 1024];
  16. }catch (Throwable e){
  17. e.printStackTrace();
  18. }finally {
  19. System.out.println(o1);
  20. System.out.println(softReference.get());
  21. }
  22. }
  23. public static void main(String[] args) {
  24. softRefMemoryNotEnough();
  25. }
  26. }

image.png

四、弱引用WeakReference(发生GC就回收)

弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。

4.1 代码演示

  1. import java.lang.ref.WeakReference;
  2. public class WeakReferenceDemo {
  3. public static void main(String[] args) {
  4. Object o1 = new Object();
  5. WeakReference<Object> weakReference = new WeakReference<>(o1);
  6. System.out.println(o1);
  7. System.out.println(weakReference.get());
  8. o1 = null;
  9. System.gc();
  10. System.out.println("*********************************");
  11. System.out.println(o1);
  12. System.out.println(weakReference.get());
  13. }
  14. }

image.png

4.2 软引用和弱引用的使用场景

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

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

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

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

4.3 WeakHashMap

WeakHashMap属于java.util包,Key为弱引用类型,一旦发生GC则会被回收

  1. import java.util.HashMap;
  2. import java.util.WeakHashMap;
  3. public class WeakHashMapDemo {
  4. public static void main(String[] args) {
  5. hashMap();
  6. System.out.println("***************分割线****************");
  7. WeakHashMap();
  8. }
  9. private static void hashMap(){
  10. HashMap<String,String> map = new HashMap<>();
  11. String key = new String("HashMapKey");
  12. String value = "HashMapValue";
  13. map.put(key,value);
  14. System.out.println("第一次打印:"+map);
  15. key = null;
  16. System.out.println("第二次打印,key引用赋值为null:"+map);
  17. System.gc();
  18. System.out.println("第三次打印,GC后:"+map+" size:"+map.size());
  19. }
  20. private static void WeakHashMap(){
  21. WeakHashMap<String,String> map = new WeakHashMap<>();
  22. String key = new String("WeakHashMapKey");
  23. String value = "WeakHashMapValue";
  24. map.put(key,value);
  25. System.out.println("第一次打印:"+map);
  26. key = null;
  27. System.out.println("第二次打印,key引用赋值为null:"+map);
  28. System.gc();
  29. System.out.println("第三次打印,GC后:"+map+" size:"+map.size());
  30. }
  31. }

image.png

五、引用队列ReferenceQueue(配合引用工作)

对象回收前需要被引用队列保存,其实通俗点就是,一个死刑犯,缓期3天执行,那么这个死刑犯需要关押在指定监狱(ReferenceQueue)等待执行,因为有3天缓期,在这3天内需要死刑犯处理后事。这就是回收前需要被队列保存。
image.png

5.1 代码演示


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

public class ReferenceQueueDemo {
    public static void main(String[] args) {
        Object o1 = new Object();
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
        WeakReference<Object> weakReference = new WeakReference<>(o1,referenceQueue);
        System.out.println("***************GC回收前***************");
        System.out.println(o1);
        System.out.println(weakReference.get());
        System.out.println(referenceQueue.poll());//队列进行消费

        System.out.println("***************启动GC***************");
        o1 = null;
        System.gc();
        try {
            Thread.sleep(500); //确保GC都执行完了
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(o1);
        System.out.println(weakReference.get());
        System.out.println(referenceQueue.poll());

    }
}

image.png

六、虚引用/幽灵引用PhantomReference(与引用队列配合使用)

虚引用需要java.lang.ref.PhantomReference类来实现,虚引用顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会觉定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。
虚引用的主要作用是跟踪对象被垃圾回收的状态,仅仅是提供了一种确保对象被finalize以后,做某些事情的机制。PhantomRefernce的get方法总是返回null,因此无法访问对应的引用对象,其意义在于说明一个对象已经进入finalization阶段,可以被gc回收,用来实现比finalization机制更灵活的回收操作。换句话说,设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步处理。Java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

6.1 代码演示


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

public class PhantomReferenceDemo {
    public static void main(String[] args) {
        Object o1 = new Object();
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
        PhantomReference<Object> phantomReference = new PhantomReference<>(o1,referenceQueue);
        System.out.println("***************GC回收前***************");
        System.out.println(o1);
        System.out.println(phantomReference.get());
        System.out.println(referenceQueue.poll());

        System.out.println("***************启动GC***************");
        o1 = null;
        System.gc();
        try {
            Thread.sleep(500); //确保GC都执行完了
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(o1);
        System.out.println(phantomReference.get());
        System.out.println(referenceQueue.poll());
    }
}

image.png

七、总结

java提供了4种引用类型,在垃圾回收的时候,都有自己各自的特点。ReferenceQueue是用来配合引用工作的,没有ReferenceQueue一样可以运行。
创建引用时候可以指定关联的队列,当GC释放对象内存的时候,会将引用加入到引用队列,如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象内存被回收之前采取必要行动这相当于是一种通知机制。
当关联的引用队列中有数据的时候,意味着引用指向的堆内存中的对象被回收。通过这种方式,JVM允许我们在对象被销毁后,做一些我们自己想做的事情。
image.png