Java中的引用类型(强引用、软引用、弱引用、虚引用)介绍,示例WeakHashMap的使用【享学Java】(中)

简介: Java中的引用类型(强引用、软引用、弱引用、虚引用)介绍,示例WeakHashMap的使用【享学Java】(中)

ReferenceQueue


软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用本身加入到与之关联的引用队列中,方便你手动释放内存。


上例中SoftReference sf对象的内存回收的时候会经历一个过程:从Active->Pending->Enqueued->Inactive。pending状态就是等待着进入ReferenceQueue队列的这样一个状态,说白了目标对象目前还没被回收,只是对象的引用(此处的obj对象)被移除了(=null),pending保存了这个引用,并且放进ReferenceQueue里(更详细的可咨询JVM的对象回收流程)。


这段话建议多读几遍,好好理解一番。 引用对象指向的对象 GC 会自动清理,但是引用对象本身也是对象(是对象就占用一定资源),所以需要我们自己手动清理哦~


so如下示例代码手动清理引用本身(强烈推荐这么做):


public class Main {
    public static void main(String[] args) throws InterruptedException {
        //定义一个引用队列
        ReferenceQueue<Person> queue = new ReferenceQueue<>();
        Person obj = new Person("fsx", 18);
        SoftReference sf = new SoftReference<>(obj, queue); // 通过构造函数把Queue传进去 让此软引用关联上队列
        obj = null; //置为null 让obj被垃圾回收期回收
        System.gc();
        TimeUnit.SECONDS.sleep(2);
        byte[] bytes = new byte[1024 * 100];
        System.gc();
        TimeUnit.SECONDS.sleep(1); // 把问题放大,让gc过来回收 保证obj已经被回收
        System.out.println("是否被回收:" + sf.get());
        //队列里存在 说明对象马上就要被回收了 所以顺势也把软引用对象干掉
        if ((sf = (SoftReference<Person>) queue.poll()) != null) {
            System.out.println("sf置为null,释放内存");
            sf = null;
        }
    }
}


输出结果:


是否被回收:null
sf置为null,释放内存


可见这么操作后,sf也被释放了。注意:若没有这句话new byte[1024 * 100],内部不会紧张,obj也不会被回收,所以sf置为null,释放内存这句话也就不会被打印了


在实际生产环境中,我们可能会用个子线程专门去处理这个回收问题。

说明:poll()方法不是阻塞方法,所以即使你在主线程里做,关系也不大。只不过这种事集中处理会比较好些


弱引用

弱引用(WeakReference):弱引用和软引用很像,当gc时,无论内存是否充足,都会回收被弱引用关联的对象。

它也可以和ReferenceQueue配合使用:如果弱引用所引用的对象被JVM回收,这个弱引用就会被加入到与之关联的引用队列中(使用方式同上示例)

public class WeakReference<T> extends Reference<T> {
  // 仅仅提供了两个构造方法而已,注意它的get方法在父类Reference上
    public WeakReference(T referent) {
        super(referent);
    }
    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}


对它多一个使用Demo如下:


public class Main {
    public static void main(String[] args) throws InterruptedException {
        Person obj = new Person("fsx", 18);
        WeakReference sf = new WeakReference(obj);
        obj = null;
        System.out.println("是否被回收" + sf.get());
        System.gc();
        System.out.println("是否被回收" + sf.get());
    }
}


输出:


是否被回收Person{name='fsx', age=18}
是否被回收null

可见即使内存是足够的,但只要GC了,它也会被回收。注意此时WeakReference sf它还在,为了避免成为垃圾,建议按照上面做处理。


说明:软引用,弱引用都非常适合来保存那些可有可无的缓存数据,如果这么做,当系统内存不足时,这些缓存数据会被回收,不会导致内存溢出。而当内存资源充足时,这些缓存数据又可以存在相当长的时间,从而起到加速系统的作用。


但是高速缓存产品建议用软引用来做,毕竟内存如果充足的话,我们是并不希望清空缓存的(如果每次gc都清空一次缓存,那么经常会出现缓存预热的情况,就一定程度上失去了缓存的意义了)


虚引用

虚引用(PhantomReference):虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期(java对象的生命周期)。

一个对象与虚引用关联,则跟没有引用与之关联一样,所以get()方法永远返回null,在任何时候都可能被垃圾回收器回收。因此它必须和ReferenceQueue一起使用,否则没有任何意义。

它的作用一般在于跟踪垃圾回收过程(netty源码中有对此种引用的使用,非常巧妙,有兴趣可自己研究一下)


public class PhantomReference<T> extends Reference<T> {
  // 它永远返回null
  @Override
    public T get() {
        return null;
    }
  // 它只有这个一个构造函数:必须和ReferenceQueue一起使用~
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}


当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在垃圾回收销毁这个对象之前,将这个虚引用加入引用队列。

因此我们可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动(比如释放资源等等操作)。


public class Main {
    public static void main(String[] args) throws InterruptedException {
        ReferenceQueue<Person> queue = new ReferenceQueue<>();
        Person obj = new Person("fsx", 18);
        PhantomReference sf = new PhantomReference<>(obj, queue);
        obj = null;
        System.out.println(sf.get());
        System.gc();
        TimeUnit.SECONDS.sleep(1);
        if ((sf = (PhantomReference<Person>) queue.poll()) != null) {
            System.out.println("obj要销毁了,准备释放内存");
            sf = null;
        }
    }
}


运行结果:


null
obj要销毁了,准备释放内存

WeakHashMap的使用


JDK1.2既然推出了这四种引用类型,那么势必是有它的使用场景的。WeakHashMap它使用的是WeakReference若引用来实现的,WeakHashMap的键是弱键,对内存友好。它的弱键实现原理步骤:


  1. 新建WeakHashMap,将“键值对”添加到WeakHashMap中(通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表)
  2. 当某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中(这个步骤尤其重要)
  3. 当下一次我们需要操作WeakHashMap时,会先同步table和queue。从table中删除调queue中被GC回收掉的那些键值对们(也就是说只有在下一次调用(任何)方法时才会来同步,才会把这个entry全部移除掉)。


从上原理可以得出,你在使用WeakHashMap时需要注意如下几点:


  1. 和HashMap一样,WeakHashMap是不同步的。可以使用 Collections.synchronizedMap 方法来构造同步的 WeakHashMap
  2. Java木有提供WeakHashSet,但你可以通过Collections.newSetFromMap(Map<E,Boolean> map)方法可以将任何 Map包装成一个Set
  3. 请不要对此Map的key有持久性的强引用,否则它就退化为HashMap了,不具有弱引用的特性了


我们平时经常使用Map来缓存数据,其实这样很多时候会造成大量的内存泄漏。下面用示例来证明这一点:


同样的,运行时请调整虚拟机参数为:-Xmx2m -Xms2m规定堆内存大小为2m。


public static void main(String[] args) throws InterruptedException {
    Map<String, Object> map = new HashMap<>();
    for (int i = 0; i < 10000; i++) {
        map.put("key" + i, new byte[i]);
    }
    System.out.println(map.size());
}


结果如下:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  at com.fsx.maintest.Main.main(Main.java:16)


因为我们用的HashMap,所以最终内存不够就OOM了~




相关文章
|
12天前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
|
1月前
|
存储 Java
Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。
【10月更文挑战第19天】本文详细介绍了Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。HashMap以其高效的插入、查找和删除操作著称,而TreeMap则擅长于保持元素的自然排序或自定义排序,两者各具优势,适用于不同的开发场景。
44 1
|
1月前
|
存储 缓存 Java
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
73 1
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
|
1月前
|
存储 Java
什么是带有示例的 Java 中的交错数组?
什么是带有示例的 Java 中的交错数组?
47 9
|
1月前
|
Java
让星星⭐月亮告诉你,jdk1.8 Java函数式编程示例:Lambda函数/方法引用/4种内建函数式接口(功能性-/消费型/供给型/断言型)
本示例展示了Java中函数式接口的使用,包括自定义和内置的函数式接口。通过方法引用,实现对字符串操作如转换大写、数值转换等,并演示了Function、Consumer、Supplier及Predicate四种主要内置函数式接口的应用。
27 1
|
1月前
|
Java API 网络安全
Java 发送邮件示例
本示例展示了如何使用Java编程语言发送电子邮件。通过利用JavaMail API,这段代码实现了从配置SMTP服务器,设置邮件属性,到发送邮件的全过程,为开发者提供了实用的参考。
|
1月前
|
存储 Java 程序员
【一步一步了解Java系列】:何为数组,何为引用类型
【一步一步了解Java系列】:何为数组,何为引用类型
25 1
|
30天前
|
存储 Java 编译器
[Java]基本数据类型与引用类型赋值的底层分析
本文详细分析了Java中不同类型引用的存储方式,包括int、Integer、int[]、Integer[]等,并探讨了byte与其他类型间的转换及String的相关特性。文章通过多个示例解释了引用和对象的存储位置,以及字符串常量池的使用。此外,还对比了String和StringBuilder的性能差异,帮助读者深入理解Java内存管理机制。
21 0
|
9天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
8天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
下一篇
无影云桌面