java 的四种引用类型

2018-06-08 22:53:46   最后更新: 2018-06-08 22:53:46   访问数量:226




在 java 对象存活判定算法的日志中,我们介绍了java 引用的分类

java 对象存活判定算法

本篇日志中,我们深入讨论一下四种引用究竟有什么区别,以及如何指定具体的引用方式

 

正如在上面日志中介绍的,在Java中,虽然不需要程序员手动去管理对象的生命周期,但是如果希望某些对象具备一定的生命周期的话(比如内存不足时JVM就会自动回收某些对象从而避免OutOfMemory的错误)就需要用到软引用和弱引用了

 

代码中普遍存在的,Object obj = new Object() 所创建的引用,只要强引用存在,垃圾收集器就永远不会回收被引用对象

当内存不足的时候,jvm 就会抛出 OutOfMemory 错误,而不会回收强引用的对象

 

强引用的断裂

只有以下两种方式可以让强引用中断,从而让 jvm 在合适的时间就会回收该对象

  1. 显式赋值为 null 会中断强引用和对象之间的关联
  2. 在一个方法内部创建的对象,由于强引用保存在栈上,所引用的对象保存在堆空间中,当这个方法运行完成就会退出方法栈,从而让强引用断裂

 

如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存

通常,软引用可用来实现内存敏感的高速缓存

 

创建软引用

String str = new String("abc"); // 强引用 SoftReference<String> softRef = new SoftReference<String>(str); // 软引用

 

 

适用场景

正如我们前面说的,软引用最常用的场景是高速缓存

例如对于已经加载过的网页,如果我们通过强引用来实现,在进入新页面时,老页面就有两种选择:主动赋值为 null 或什么都不做

如果赋值为 null,那么点击回退就必须重新加载原网页,造成体验不佳,而如果什么都不做,大量的加载过的网页停留在内存中,最终就会造成 OutOfMemory 错误

这时,通过软引用,让已加载过的页面在内存不足时被自动回收,只要内存充足就可以任意回退而不需要重新加载,就是一个非常好的选择

 

软引用可以配合引用队列进行使用,当软引用占有的对象被回收后,jvm 会将该软引用对象放入引用队列中

通过软引用队列,我们可以清楚地知道程序中已有的软引用哪些已经被回收掉了

SoftReference<T> ref = new SoftReference<T>(T t, ReferenceQueue refQueue);

 

 

通过软引用队列实现软引用缓存

我们结合软引用与软引用队列来实现一个 KV 缓存

 

  • 缓存接口

我们先来定义最基本的 KV 缓存规范接口

public interface Cache<K, V> { public V get(K key); public boolean set(K key, V value); }

 

 

只需要一个 get 接口和一个 set 接口即可

 

  • 软引用的实现

软引用只是对一个对象的引用,我们需要 K-V 的组合,因此仅仅使用软引用是不够的,需要对软引用添加额外信息,因此我们继承软引用来实现自己的软引用

public class ExtraInfoReference<T> extends SoftReference<T> { private Object info; public ExtraInfoReference(Object info, T t, ReferenceQueue<T> refQueue) { super(t, refQueue); this.info = info; } public Object getExtraInfo() { return this.info; } }

 

 

  • 软引用缓存
public abstract class SoftRefCache<K, V> implements Cache<K, V> { // 缓存,用软引用记录 private ConcurrentHashMap<K, ExtraInfoReference<V>> cache = new ConcurrentHashMap<K, ExtraInfoReference<V>>(); private ReferenceQueue<V> refQueue = new ReferenceQueue<V>(); public V get(K key) { V value = null; if (cache.containsKey(key)) { ExtraInfoReference<V> refValue = cache.get(key); value = refValue.get(); } // 如果软引用被回收 if (value == null) { // 清除软引用队列 clearRefQueue(); } return value; } /** * 实现set方法 */ public boolean set(K key, V value) { ExtraInfoReference<V> refValue = new ExtraInfoReference<V>(key, value, refQueue); cache.put(key, refValue); return true; } /** * 从软引用队列中移除无效引用, * 同时从cache中删除无效缓存 */ protected void clearRefQueue() { ExtraInfoReference<V> refValue = null; while ((refValue = (ExtraInfoReference<V>) refQueue.poll()) != null) { K key = (K) refValue.getExtraInfo(); cache.remove(key); } } }

 

 

我们通过 ConcurrentHashMap 实现了一个 K-V 存储,同时通过 ExtraInfoReference 设置 info 成员为 key,将软引用设置为 value 的方式记录了所有在缓存中的数据

这样,当我们调用 get 方法发现 value 为 null 的时候,我们可以轻松地通过软引用队列来对缓存进行清理

需要注意的是,调用 get 方法时,value = refValue.get() 会将软引用转化为强引用

 

与软引用相比,只具有弱引用的对象拥有更短暂的生命周期,不管当前内存空间是否充足,只要进行垃圾回收,弱引用的对象都会被回收

通常只有创建时不一定会被使用到的对象应该设置为弱引用,只有当被使用到时,即通过赋值,转化为强引用,例如回调方法中的对象是一个典型场景,回调方法通常是通过匿名内部类实现的,如果他们持有外部类的强引用就造成了内存泄露

WeakReference<T> ref = new WeakReference<T>(T t);

 

 

与软引用一样,也可以通过传入 ReferenceQueue 对象指定销毁后存放的队列

WeakReference<T> ref = new WeakReference<T>(T t, ReferenceQueue refQueue);

 

 

虚引用也称为“幽灵引用”或“幻影引

无法通过虚引用获取一个对象的实例,因此它的存在只能用来跟踪垃圾回收器的回收活动

虚引用必须与引用队列联合使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中

PhantomReference<T> ref = new PhantomReference<T>(T t, ReferenceQueue refQueue);

 

 

四种引用类型及生存时间
引用类型被回收时间生存时间备注
强引用不会被回收jvm 停止运行时终止普通赋值
软引用内存不足时回收内存不足时 gc 后终止通常用作对象缓存
弱引用垃圾回收时被回收gc 运行后终止通常用作防止内存泄露,如匿名内部类对外部类对象的引用
虚引用不能实例化

 

 

 

深入理解 Java 虚拟机 -- jvm 高级特性与最佳实践(第 2 版)

https://www.cnblogs.com/gudi/p/6403953.html

https://blog.csdn.net/jiangjiajian2008/article/details/52929843

 






技术帖      java      gc      垃圾回收      引用      reference      弱引用      软引用      强引用      虚引用     


京ICP备15018585号