引用类型有哪几种,Java引用类型详解
在Java中一切皆对象,对象的操作是通过该对象的引用(Reference)实现的
Java中的引用类型有4种,分别是强引用、软引用、弱引用和虚引用(强、软、弱、虚)
一、整体架构
二、强引用(默认)
Java中最常见的就是强引用,在把一个对象赋给一个引用变量时,这个引用变量就是一个强引用(Object obj = new Object(),obj就是一个强引用,obj在栈中,new Object()在堆中)
有强引用的对象一定为可达性状态
即便系统内存非常紧张,Java虚拟机宁愿抛出OutOfMemoryError(OOM)错误,使程序异常终止,也不会回收被强引用所引用的对象
强引用不会被垃圾回收机制直接回收,需要处理,因此强引用是造成内存泄露的主要原因
如果强引用对象不使用时,需要弱化从而使垃圾回收器能够回收,弱化的方式就是给引用变量赋为null,让其超出对象的生命周期范围,则垃圾回收器认为该对象不存在引用,这时就可以回收这个对象,具体什么时候收集取决于垃圾回收器算法
1、使用
public class StronglyReferenceTest { public static void main(String[] args) throws IOException { // obj1、obj2存放在栈中 Object obj1 = new Object(); Object obj2 = obj1; obj1 = null; System.gc(); // obj1为null被回收,但new Object()还是存放在堆中,被obj2强引用 System.out.println(obj1); // 不会被垃圾回收 System.out.println(obj2); // 阻塞main线程,给垃圾回收线程一点时间去执行 System.in.read(); } }
2、补充finalize()
finalize()是Object中的方法,当垃圾回收器将要回收对象所占内存之前被调用,即当一个对象被JVM宣告死亡时会先调用它finalize()方法
Java使用finalize()方法在垃圾回收器将对象从内存中清除出去之前做必要的清理工作
3、注意
(1)全局变量和局部变量
在一个方法的内部有一个强引用,这个引用保存在Java栈中,而真正的引用内容(Object)保存在Java堆中 ,当这个方法运行完成后,就会退出方法栈,则引用对象的引用数为0,这个对象会被回收
但是如果这个强引用是全局变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收
(2)内存溢出和内存泄漏
内存溢出 OutOfMemory,是指程序在申请内存时,没有足够的内存空间供其使用,出现OutOfMemoryError比如申请了一个Integer,但给它存了long才能存下的数,那就是内存溢出(强引用引发的错误)
内存泄露 MemoryLeak,是指程序在申请内存后,无法释放已申请的内存空间(强引用导致的结果)
大量的内存泄露会导致内存溢出(OOM)
(3)回收问题
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显示地将相应强引用赋值给null,一般认为就是可以被垃圾回收器收集了,具体还需要看垃圾回收的策略
三、软引用
软引用通过SoftReference类实现,如果一个对象只有软引用,则在系统内存空间不足时该对象将被回收
这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等
1、使用
JVM配置:-Xms10M -Xmx25M -XX:+PrintGCDetails
import java.lang.ref.SoftReference; public class SoftReferenceTest { public static void main(String[] args) { // 10M SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024 * 1024 * 10]); System.out.println(softReference.get()); // 手动回收 System.gc(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } // 执行手动回收后,并没有回收 System.out.println(softReference.get()); // 15M 导致内存不足 自动回收 byte[] b = new byte[1024 * 1024 * 15]; System.out.println(softReference.get()); } }
2、缓存设计思路
用图片缓存为例,用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的内存空间,从而有效地避免了OOM的问题
Map<String, SoftReference<Bitmap>> imageCache = new HashMap<>();
四、弱引用
弱引用通过WeekReference类实现,如果一个对象只有弱引用,则在垃圾回收过程中一定会被回收,也就是不管JVM的内存空间是否足够,都会回收弱引用类型的对象所占用的内存空间
弱引用的生命周期比软引用的生命周期更短
1、使用
import java.lang.ref.WeakReference; public class WeakReferenceTest { public static void main(String[] args) { WeakReference<Object> weakReference = new WeakReference<>(new Object()); System.out.println(weakReference.get()); System.gc(); // null System.out.println(weakReference.get()); } }
2、ThreadLocal
Java里,每个线程(Thread)都有自己的ThreadLocalMap,里边存着自己私有的对象
Map的Entry里,key为ThreadLocal对象,value即为私有对象
static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
每个Thread内部都维护一个ThreadLocalMap字典数据结构,字典的Key值是ThreadLocal,那么当某个ThreadLocal对象不再使用(没有其它地方再引用)时,每个已经关联了此ThreadLocal的线程怎么在其内部的ThreadLocalMap里做清除此资源呢?
JDK中的ThreadLocalMap没有继承java.util.Map类,而是自己实现了一套专门用来定时清理无效资源的字典结构
其内部存储实体结构Entry<ThreadLocal, T>继承自java.lan.ref.WeakReference,这样当ThreadLocal不再被引用时,因为弱引用机制原因,当JVM发现内存不足时,会自动回收弱引用指向的实例内存,即其线程内部的ThreadLocalMap会释放其对ThreadLocal的引用从而让JVM回收ThreadLocal对象
这里是重点强调下,是回收对ThreadLocal对象,而非整个Entry,所以线程变量中的值T对象还是在内存中存在的,所以内存泄漏的问题还没有完全解决
调用ThreadLocal.get()或者ThreadLocal.set(T)时都会定期执行回收无效的Entry操作
3、WeakHashMap
import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; public class WeakHashMapTest { public static void main(String[] args) { Map<Integer, String> hashMap =new HashMap<>(); Integer key1 = new Integer("1"); String value1 = "HashMap"; hashMap.put(key1, value1); System.out.println(hashMap); key1 = null; // 只跟new Integer("1")有关,跟Map无关系,不影响 HashMap System.out.println(hashMap); System.gc(); // 不影响 HashMap System.out.println(hashMap); System.out.println("----------"); Map<Integer, String> weakHashMap =new WeakHashMap<>(); Integer key2 = new Integer("2"); String value2 = "WeakHashMap"; weakHashMap.put(key2, value2); System.out.println(weakHashMap); key2 = null; // 只跟new Integer("2")有关,跟Map无关系,不影响 WeakHashMap System.out.println(weakHashMap); System.gc(); // 影响 WeakHashMap System.out.println(weakHashMap); } }
4、ReferenceQueue
import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; public class ReferenceQueueTest { public static void main(String[] args) { Object obj = new Object(); ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); WeakReference<Object> weakReference = new WeakReference<>(obj, referenceQueue); System.out.println(obj); System.out.println(weakReference.get()); System.out.println(referenceQueue.poll()); // null System.out.println("----------"); obj = null; System.gc(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(obj); // null System.out.println(weakReference.get()); // null System.out.println(referenceQueue.poll()); } }
五、虚引用
与其他三种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被回收
PhantomReference的get方法总是返回null,因此无法访问对应的引用对象,它的意义在于说明一个对象已经进入finalization阶段,可以被垃圾回收器回收,用来实现比finalization机制更灵活的回收操作
虚引用通过PhantomReference类实现,虚引用和引用队列联合使用
虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用
当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中
设置虚引用关联的唯一目的就是在这个对象被回收的时候,收到一个系统通知或者后续添加进一步的处理
Object object = new Object(); ReferenceQueue queue = new ReferenceQueue (); PhantomReference pr = new PhantomReference (object, queue);
六、总结
当垃圾回收器回收时,某些对象会被回收,某些不会被回收
垃圾回收器会从根对象Object来标记存活的对象,然后将某些不可达的对象和一些引用的对象进行回收
引用类型 | 被垃圾回收时间 | 用途 | 生存时间 |
---|---|---|---|
强引用 | 从来不会 | 对象的一般状态 | JVM停止运行时终止 |
软引用 | 当内存不足时 | 对象缓存 | 内存不足时终止 |
弱引用 | 正常垃圾回收时 | 对象缓存 | 垃圾回收后终止 |
虚引用 | 正常垃圾回收时 | 跟踪对象的垃圾回收 | 垃圾回收后终止 |
本文地址:百科问答频道 https://www.neebe.cn/wenda/907993.html,易企推百科一个免费的知识分享平台,本站部分文章来网络分享,本着互联网分享的精神,如有涉及到您的权益,请联系我们删除,谢谢!