精华内容
下载资源
问答
  • 弱引用
    千次阅读
    2021-11-27 11:42:42

    Java 中的引用

    一、 概述

    几乎所有允许动态分配内存的计算机语言都会遇见一个共同问题——如何“收集”不在使用的内存。
    有点和餐厅类似,开始时,餐厅的所有餐桌均处于空闲状态,可供顾客使用。但是,当所有餐桌都已经被分配给客户时,就需要检查哪些已经被分配的餐桌处于空闲状态。
    有些语言,比如 C 语言,把这个责任交给用户:分配到了内存,那么你就有责任释放内存。这就和快餐很像,用完餐后,需要把餐桌整理干净。如果所有顾客都这样正确处理,这种方式效率很高,但是,如果有顾客忘记清理餐桌,就会出现问题。内存空间也一样,很容易忘记释放已经分配的内存。
    垃圾收集器(GC)就用来解决此类问题。在 Java 中,GC 使用特定算法“收集”不在使用的内存,程序员不需要手动释放。
    假设 Java GC 完美运行,并且它能释放所有不再可达的对象。那么,就会引入一个新问题——想保留对一个对象的引用,但是当该对象没有其余引用时,又不想阻止 GC 对其回收。就像在餐厅用完餐后还想坐一会,但是当有新顾客需要这张餐桌时,可以立即离开。

    二、Java 四种引用

    为了解决这类问题,Java 在原来强引用基础上新引入了三类应用——软引用(SoftReference),弱引用(WeakReference)和虚引用(PhantomReference)。看下 Java Doc 如何介绍:

    • 软引用 SoftReference,由 GC 根据内存需求自行清除软引用对象。 软引用最常用于实现内存敏感缓存。在 JVM 抛出 OOM 之前,保证对所有软引用可达对象都已被回收。否则,对 SoftReference 引用对象回收时间以及不同 SoftReference 引用对象回收顺序没有限制;
    • 弱引用 WeakReference,弱引用不会阻止其引用对象标记为可终结状态、被终结以及被回收。 弱引用最常用于实现规范化映射。假设,GC 在某个时间点确定某个对象只有弱引用可达,届时,它自动地将所有指向该对象的所有弱引用清除,并且,如果该对象通过弱引用或强引用链所引用的对象也变成只有弱引用可达,GC也会将这些将指向这些对象的弱引用清除。同时,GC 会将刚才形成的只有弱引用可达对象标记为可终结状态。同时或者稍后,GC 会将刚才清除的弱引用加入到创建时注册的引用队列中。
    • 虚引用 PhantomReference,如果 GC 在某个时间点确定虚引用的所指对象只有虚引用可达,届时或稍后将其加入引用队列。为了确保可回收对象可继续使用,无法通过虚引用获取其引用对应,即虚引用的 get 方法始终返回 null。和软引用以及虚引用不同,在其加入引用队列时,不会自动被 GC 清除。通过虚引用可达对象始终是可达的,直到所有虚引用被清除或者本身变得不可达。

    简而言之:软引用尝试保留其引用对象,弱引用不会试图保留其引用对象,虚引用所引用对象不会被被释放直到所有指向该对象的虚引用被清除。

    再次以餐厅比喻:SoftReference 就像一位顾客说,“只有当没有其他餐桌可用时,我才会离开我的餐桌。” WeakReference 就像一个顾客在新顾客到来时准备离开。 PhantomReference 就像一个顾客准备在新顾客到来时立即离开,但实际上直到经理允许他才离开。

    三、SoftReference 软引用

    1. 真的全部回收吗?

    官方Java Doc指出,在 JVM 抛出 OOM 之前,保证对所有软引用可达对象都已被回收。

    All soft references to softly-reachable objects are guaranteed to have been cleared before the virtual machine throws an OutOfMemoryError.

    真的是这样吗?

    在 java 1.2 中首次引入软引用时确实如此,但是从 java 1.3.1 开始引入了 jvm 参数 -XX:SoftRefLRUPolicyMSPerMB(默认为10ms),如果该值设置为0,那么Java Doc中这句话就没问题了。
    软引用对象能够被回收需要满足一定的逻辑判断,判断公式如下:

    clock - timestamp <= freespace * SoftRefLRUPolicyMSPerMB
    

    clock表示上次 GC 的时间戳,timestamp 表示最近一次读取软引用对象的时间戳,这两者的差值表示该软引用对象多久没被使用了,差值越大,软引用对象价值越低,负数则表示软引用对象刚刚被使用。freespace 是空闲空间大小,SoftRefLRUPolicyMSPerMB 表示每一 MB 的空闲内存空间可以允许软引用对象存活多久。
    如果SoftRefLRUPolicyMSPerMB 取默认值1 ms,这意味着如果只有 10MB 可用堆内存,GC 将释放已使用超过 10 秒的引用。

    2. 应用场景

    正如Java Doc 说明, 软引用最常用于实现内存敏感缓存,当堆内存不足时,释放缓存空间。
    这里分享Spring 缓存配置属性缓存,配置属性在应用启动时读取,运行过程中很有可能不会再次使用,所以Spring 大佬使用软引用缓存。

    
    class SoftReferenceConfigurationPropertyCache<T> implements ConfigurationPropertyCaching {
    
    	private static final Duration UNLIMITED = Duration.ZERO;
    
    	private final boolean neverExpire;
    
    	private volatile Duration timeToLive;
    
    	private volatile SoftReference<T> value = new SoftReference<>(null);
    
    	private volatile Instant lastAccessed = now();
    
    	SoftReferenceConfigurationPropertyCache(boolean neverExpire) {
    		this.neverExpire = neverExpire;
    	}
    
    	@Override
    	public void enable() {
    		this.timeToLive = UNLIMITED;
    	}
    
    	@Override
    	public void disable() {
    		this.timeToLive = null;
    	}
    
    	@Override
    	public void setTimeToLive(Duration timeToLive) {
    		this.timeToLive = (timeToLive == null || timeToLive.isZero()) ? null : timeToLive;
    	}
    
    	@Override
    	public void clear() {
    		this.lastAccessed = null;
    	}
    
    	/**
    	 * Get an value from the cache, creating it if necessary.
    	 * @param factory a factory used to create the item if there is no reference to it.
    	 * @param refreshAction action called to refresh the value if it has expired
    	 * @return the value from the cache
    	 */
    	T get(Supplier<T> factory, UnaryOperator<T> refreshAction) {
    		T value = getValue();
    		if (value == null) {
    			value = refreshAction.apply(factory.get());
    			setValue(value);
    		}
    		else if (hasExpired()) {
    			value = refreshAction.apply(value);
    			setValue(value);
    		}
    		if (!this.neverExpire) {
    			this.lastAccessed = now();
    		}
    		return value;
    	}
    
    	private boolean hasExpired() {
    		if (this.neverExpire) {
    			return false;
    		}
    		Duration timeToLive = this.timeToLive;
    		Instant lastAccessed = this.lastAccessed;
    		if (timeToLive == null || lastAccessed == null) {
    			return true;
    		}
    		return !UNLIMITED.equals(timeToLive) && now().isAfter(lastAccessed.plus(timeToLive));
    	}
    
    	protected Instant now() {
    		return Instant.now();
    	}
    
    	protected T getValue() {
    		return this.value.get();
    	}
    
    	protected void setValue(T value) {
    		this.value = new SoftReference<>(value);
    	}
    
    }
    
    

    三、WeakReference 弱引用

    GC 一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都将其会回。但是注意Java Doc指出,GC 会将清除的弱引用加入到创建时注册的引用队列中。

    例如,Student 类包含 String 类型属性 name,和 int 类型属性 age,如下程序

    ReferenceQueue<String> strQueue = new ReferenceQueue<>();
    ReferenceQueue<Student> stuQueue = new ReferenceQueue<>();
    WeakReference<String> name = new WeakReference<String>(new String("fxs"), strQueue);
    WeakReference<Student> sr = new WeakReference<Student>(new Student(name.get(), 12), stuQueue);
    System.err.println(name.hashCode()); // 1973336893
    System.err.println(sr.hashCode()); // 1212899836
    System.err.println(sr.get()); // Student{name='fxs', age=12}
    System.gc();                //通知JVM的gc进行垃圾回收
    System.err.println(sr.get()); // null
    System.err.println(name.get()); // null
    
    Thread.sleep(1000); // 软引用可能稍后添加到引用队列
    
    System.err.println(strQueue.poll().hashCode()); // 1973336893
    System.err.println(stuQueue.poll().hashCode()); // 1212899836
    

    GC 进行垃圾回收时,srhashcode1973336893)只有弱引用可达,GC 会将其清除,并加入到创建 sr 时注册的引用队列stuQueue,在垃圾回收结束后可以通过stuQueue获取到该软引用。
    同时sr 所引用的 name 属性也只有软引用 namehashcode1212899836),GC也会将其清除,并加入到创建 sr 时注册的引用队列strQueue中。

    弱引用有哪些应用场景呢?
    考虑如下场景:
    在开发中,可能会遇到将一个类使用修饰符 final 将其标识为不可拓展。或者一个对象是通过静态工厂方法返回的一个接口。这时,这个类不能被继承,所以不能添加新功能。那如果想存储与之关联的额外信息怎么办呢?
    例如,有一个用 final 修饰的类 Product, 现在想记录该类每个对象的序列号。但是由于 Product 类并不包含这个属性,而且也不能扩展导致我们也不能增加这个属性。其实一点问题也没有,HashMap完全可以解决上述的问题。

    serialNumberMap.put(product, productSerialNumber);
    

    这表面看上去没有问题,但是 serialNumberMapproduct 对象的强引用很有可能会引发问题。当一个 product 不需要时,我们应该将这个条目从 map 中移除。如果我们没有移除的话,可能会导致内存泄露。
    一种解决方案是使用消息通知机制——product 销毁时,通知 serialNumberMap 删除。

    另外一种比较高效的解决方案是使用弱引用。
    可以考虑将 serialNumberMap 键封装在弱引用 WeakReference中。当 map 中某个product 只有弱引用可达是,“弱键”用并不会阻止 GC 将其回收,并在GC回收该“弱键”时,这个“弱键”也同时会被添加到引用队列中。当下一次需要操作 map 时,删除 map 中被已经被回收了的“弱键”(存在引用中)所对应的键值对。
    这些已经被java.util.WeakHashMap封装好了,可以直接拿来用,但是需要知道的是,其线程不安全。

    四、PhantomReference 虚引用

    注意Java Doc对虚引用的说明——如果 GC 在某个时间点确定虚引用的所指对象只有虚引用可达,届时或稍后将其加入引用队列。和软引用以及虚引用不同,在其加入引用队列时,不会自动被 GC 清除。

    虚引用所引用对象不会被被释放直到所有指向该对象的虚引用被清除,所以, 虚引用通常用来跟踪对象被垃圾回收的活动。

    比如,虚引用可用于在某些对象超出范围以执行某些资源清理时通知您。
    Object.finalize() 方法不能保证在对象生命周期结束时被调用,所以如果需要关闭文件或释放资源,你可以依赖虚引用。
    由于虚引用没有指向实际对象的链接,因此典型的方式是从 PhantomReference 继承出自己的引用类型,并添加一些对资源释放用的信息,比如文件名。

    参考

    更多相关内容
  • 很早Java API就添加了弱引用(WeakReference)和软引用(SoftReference),但并不是所有的程序员都熟悉这两个概念
  • 主要介绍了C语言中的强符号、弱符号、强引用和弱引用的定义及相关内容,非常的简单易懂,有需要的朋友可以参考下
  • 它使用LRU / LFU(LRFU)到期来保留引用的数据,然后,一旦数据处于非活动状态,它将使用弱引用(和终结注册表)来允许GC作为正常GC周期的一部分删除缓存的数据,但仍然继续只要数据仍驻留在内存中并且尚未被收集,...
  • 将带大家快速理解Java中弱引用,文章介绍的很详细,对大家学习Java很有帮助哦,有需要的可以参考借鉴。
  • Java从JDK1.2版本开始,就把对象的引用分为四种级别,从而使程序能更加灵活的...这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。本文给大家介绍Android利用软引用和弱引用避免OOM,需要的朋友一起学习吧
  • 本篇文章尝试从What、Why、How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义、基本使用场景和使用方法。由于个人水平有限,叙述中难免存在不准确或是不清晰的地方,希望大家可以指出,谢谢大家:)  1....
  • 详解 JAVA 弱引用

    2020-08-18 14:19:57
    主要介绍了 JAVA 弱引用的相关资料,帮助大家更好的理解和学习java引用对象,感兴趣的朋友可以了解下
  • Java弱引用与WeakHashMap

    2020-12-22 22:11:59
     《Java 理论与实践: 用弱引用堵住内存泄漏》一文也指出了使用全局的Map作为缓存容器时发生的内存泄露问题,介绍了如何使用hprof工具来找出内存泄露,并分析了如何使用弱引用来防止内存泄露,还分析了
  • Lua中的弱引用介绍

    2020-09-22 05:04:32
    主要介绍了Lua中的弱引用介绍,本文用一个实例讲解了Lua弱引用的相关知识,需要的朋友可以参考下
  • referenceInJava:Java强引用,软引用,弱引用以及虚引用测试项目
  • 智能指针有很多实现方式,android 中的sp 句柄类实际上就是google 实现的一种强引用的智能指针。我没有仔细看android sp 的实现方式,但其基本原理是固定的,现在我们从一个相对简单的例子来看智能指针的实现
  • } } 运行的结果: 进行gc时,强引用与弱引用同时指向value内存区域:value 未进行gc时,只有弱引用指向value内存区域:value 进行gc时,只有弱引用指向value内存区域:null 这里有个前置知识说下,当要获得...

    概念

    大部分情况下我们看到是强引用,比如下面这一行:

    String str1 = new String("abc");
    

    变量str1被用来存放一个string对象的强引用上。强引用在你正在使用时这个对象时,一般是不会被垃圾回收器回收的。当出现内存空间不足时,虚拟机不会释放强引用的对象占用的空间,而是选择抛出异常(OOM)。

    什么时候会回收强引用的空间呢,就是没有引用的时候,比如你这样写:

    str1 = null
    

    GC在适当的时候就会回收str1指向的空间。

    而弱引用(Weak Reference)的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

    java中使用弱引用的语法是:

    String str1 = new String("abc");
    WeakReference<String> weakReference = new WeakReference<>(str1);
    

    深入原理

    我们先来通过一个案例,看下gc对于弱引用的回收策略。

    public class App {
    
        public static WeakReference<String> weakReference1;
    
        public static void main(String[] args) {
    
            test1();
            //test1外部,hello对象作用域结束,没有强引用指向"value"了。只有一个弱引用指向"value"
            System.out.println("未进行gc时,只有弱引用指向value内存区域:" + weakReference1.get());
    
            //此时gc时会回收弱引用
            System.gc();
            
            //此时输出都为nuill
            System.out.println("进行gc时,只有弱引用指向value内存区域:" + weakReference1.get());
    
        }
    
        public static void test1() {
            //hello对象强引用"value"
            String hello = new String("value");
    
            //weakReference1对象弱引用指向"value"
            weakReference1 = new WeakReference<>(hello);
    
            //在test1内部调用gc,此时gc不会回收弱引用,因为hello对象强引用"value"
            System.gc();
            System.out.println("进行gc时,强引用与弱引用同时指向value内存区域:" + weakReference1.get());
    
        }
    
    }
    

    运行的结果:

    
    进行gc时,强引用与弱引用同时指向value内存区域:value
    未进行gc时,只有弱引用指向value内存区域:value
    进行gc时,只有弱引用指向value内存区域:null
    

    这里有个前置知识说下,当要获得WeakReference的object时, 首先需要判断它是否已经被GC回收,若被收回,则下列返回值为空:

    weakReference1.get();
    

    根据这个结果,我们可以得出这样的结论:

    • 当有强引用指向value内存区域时,即使进行gc,弱引用也不会被释放,对象空间不回被回收。

    • 当无强引用指向value内存区域是,进行gc,弱引用会被释放,对象空间将会执行回收流程。

    我们接着从源码层面看下弱引用。首先引入一个概念叫gc的可达性。因为本篇文章并不是专门讲gc的,所以我这里并不打算展开太多这部分,知识一句话概括下gc可达性的概念。

    GC决定一个对象是否可被回收,其基本思路是从GC Root开始向下搜索,如果对象与GC Root之间存在引用链,则对象是可达的,GC会根据是否可到达与可到达性决定对象是否可以被回收。

    public class WeakReference<T> extends Reference<T> {
    
        /**
         * Creates a new weak reference that refers to the given object.  The new
         * reference is not registered with any queue.
         *
         * @param referent object the new weak reference will refer to
         */
        public WeakReference(T referent) {
            super(referent);
        }
    
        /**
         * Creates a new weak reference that refers to the given object and is
         * registered with the given queue.
         *
         * @param referent object the new weak reference will refer to
         * @param q the queue with which the reference is to be registered,
         *          or <tt>null</tt> if registration is not required
         */
        public WeakReference(T referent, ReferenceQueue<? super T> q) {
            super(referent, q);
        }
    
    }
    

    WeakReference源码很简单,所以大部分逻辑都在父类Reference中。Reference类中有个核心的类是ReferenceQueue,这类的注释是这样写的:

    Reference queues, to which registered reference objects are appended by the garbage collector after the appropriate reachability changes are detected.

    翻译过来大概意思是,在检测到对象的可达性发生改变后,垃圾回收器就将已注册的引用对象添加到Reference queues队列中。

    这个类有三个public方法,enqueue,poll和remove。标准的队列操作,这个很简单不展开。

    然后再回到Reference类,它是这样定义的:

    
    /**
     * Abstract base class for reference objects.  This class defines the
     * operations common to all reference objects.  Because reference objects are
     * implemented in close cooperation with the garbage collector, this class may
     * not be subclassed directly.
     *
     * @author   Mark Reinhold
     * @since    1.2
     */
    
    public abstract class Reference<T> {
    

    首先它是一个抽象类,意味着你不能直接创建此类的实例。注释的意思是,这是引用对象的抽象基类,这个类定义了引用对象的常用操作。引用对象的实现一般都和垃圾回收器密切相关。

    这里有个很重要的信息就是这个类和垃圾回收密切相关。

    一个reference的实例有四种状态,

    Active

    这是一个受会受到GC的特别关注的状态,当GC察觉到引用的可达性变化为“合适”的状态之后,reference实例的状态将变化为Pending或Inactive,到底转化为Pending状态还是Inactive状态取决于此Reference对象创建时是否注册了queue.如果注册了queue,则将添加此实例到pending-Reference list中。而新创建的Reference实例的状态是Active。

    Pending

    在pending-Reference list中等待着被Reference-handler 线程入队列queue中的元素处于pending状态。没有注册queue的实例是永远不可能到达这一状态。

    Enqueued

    当实例创建的时候加入了队列后的状态。当实例被从ReferenceQueue中移除时,它的状态变为Inactive。没有注册ReferenceQueue的不可能到达这一状态的。

    Inactive

    终态。一旦一个实例变为Inactive,则这个状态永远都不会再被改变了。

    掌握了这四个状态,继续往下看源码,

    volatile ReferenceQueue<? super T> queue;
    

    这个queue是通过构造函数传入的,表示创建一个Reference实例时,要将其注册到那个queue上。

    然后继续看有个很重要的类,

    private static class ReferenceHandler extends Thread {
    ...
    
    
    static {
            ThreadGroup tg = Thread.currentThread().getThreadGroup();
            for (ThreadGroup tgn = tg;
                 tgn != null;
                 tg = tgn, tgn = tg.getParent());
            Thread handler = new ReferenceHandler(tg, "Reference Handler");
            /* If there were a special system-only priority greater than
             * MAX_PRIORITY, it would be used here
             */
            handler.setPriority(Thread.MAX_PRIORITY);
            handler.setDaemon(true);
            handler.start();
    
            // provide access in SharedSecrets
            SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
                @Override
                public boolean tryHandlePendingReference() {
                    return tryHandlePending(false);
                }
            });
        }
        ...
    
    

    根据代码,我们知道这个线程处理类具有最高的优先级,并且是daemon状态在跑。这个线程的逻辑就是:不断的从Reference构成的pending链表上获取Reference对象,如果pending不为null,则将pending的对象进行clean,如果注册的时候有queue就进行enqueue,否则线程进行wait状态。

    基于以上分析,我们可以总结下Reference的机制:

    pending是由JVM来赋值的,当Reference内部的referent对象的可达状态发生改变时,JVM会将Reference对象放入到pending链表中。然后启动一个ReferenceHandler线程来处理,处理的逻辑就是调用Cleaner#clean,然后根据注册时候是否有队列决定是否调用ReferenceQueue#enqueue方法进行处理。

    应用案例解析

    弱引用一般用在什么场合呢?我们可以通过一些常见的组件的源码来分析下。来看看常见的threadlocal的源码关于弱引用的使用。

    threadLocal是一个线程本地变量,每个线程维护自己的变量副本,它的实现原理简单来讲就是每个线程Thread类都有个属性ThreadLocalMap,Map是自定义实现的Entry[]数组结构,所以可以维护该线程的多个ThreadLocal变量。

    在这里插入图片描述

    图中的虚线,表示 ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key的。

    既然说是Entry,必然有key和value。其中Key即是ThreadLocal变量本身,Value则是具体该线程中的变量真实的副本值。代码如下:

    static class ThreadLocalMap {
    
            /**
             * The entries in this hash map extend WeakReference, using
             * its main ref field as the key (which is always a
             * ThreadLocal object).  Note that null keys (i.e. entry.get()
             * == null) mean that the key is no longer referenced, so the
             * entry can be expunged from table.  Such entries are referred to
             * as "stale entries" in the code that follows.
             */
            static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    

    然后你会注意到,Entry的Key即ThreadLocal对象是采用弱引用引入的。为什么ThreadLocalMap使用弱引用存储ThreadLocal呢?

    还是看上面那张图。

    ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程迟迟不结束的话(因为大部分时候我们用的都是线程池,核心线程都是长期驻留的),这些key为null的Entry的value就会一直存在一条强引用链:

    Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
    

    永远无法回收,造成内存泄漏。这样看,似乎是弱引用导致了内存泄漏?

    事实上是,无论这里使用强引用还是弱引用,都有可能造成内存泄漏。如果key 使用强引用:引用的ThreadLocal的对象如果置为null,被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。

    反过来,如果key使用了弱引用,当jvm发现内存不足时,会自动回收弱引用指向的实例内存,也就是回收对ThreadLocal对象。但是这个时候value还是存在的。不过没有关系,看源码你会发行在调用get或者set操作的时候,都有机会执行回收无效entry的操作。

    展开全文
  • 主要给大家介绍了关于在iOS中的集合该如何弱引用对象的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来依稀学习学习吧。
  • java弱引用

    2018-01-18 18:05:27
    java 弱引用代码以及分析,详细讲解弱引用与强引用在垃圾回收时产生的区别
  • 强引用、软引用、弱引用、虚引用有什么区别?具体使用场景是什么? 典型回答 1.强引用 特点:我们平常典型编码Object obj = new Object()中的obj就是强引用。通过关键字new创建的对象所关联的引用

    在 Java 语言中,除了原始数据类型的变量,其他所有都是所谓的引用类型,指向各种不同的对象,理解引用对于掌握 Java 对象生命周期和 JVM 内部相关机制非常有帮助。

    在Java语言中,除了基本数据类型外,其他的都是指向各类对象的对象引用;Java中根据其生命周期的长短,将引用分为4类。

    强引用、软引用、弱引用、虚引用有什么区别?具体使用场景是什么?

    典型回答

    1.强引用

    特点:我们平常典型编码Object obj = new Object()中的obj就是强引用。通过关键字new创建的对象所关联的引用就是强引用。 当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,具体回收时机还是要看垃圾收集策略。

    应用场景:项目中到处都是。

    2.软引用

    特点:软引用通过SoftReference类实现。 软引用的生命周期比强引用短一些。只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象:即JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。

    应用场景:软引用通常用来实现内存敏感的缓存。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。

    具体实例:图片缓存框架中,“内存缓存”中的图片是以这种引用来保存,使得JVM在发生OOM之前,可以回收这部分缓存。

    3.弱引用

    特点:弱引用通过WeakReference类实现。 弱引用的生命周期比软引用短。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快回收弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

    应用场景:弱应用同样可用于内存敏感的缓存。

    具体实例:在静态内部类中,经常会使用虚引用。例如,一个类发送网络请求,承担callback的静态内部类,则常以虚引用的方式来保存外部类(宿主类)的引用,当外部类需要被JVM回收时,不会因为网络请求没有及时回来,导致外部类不能被回收,引起内存泄漏。

    4.虚引用

    特点:虚引用也叫幻象引用,通过PhantomReference类来实现。无法通过虚引用访问对象的任何属性或函数。幻象引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

    ReferenceQueue queue = new ReferenceQueue ();
    PhantomReference pr = new PhantomReference (object, queue);
    

    程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取一些程序行动。

    应用场景:可用来跟踪对象被垃圾回收器回收的活动,当一个虚引用关联的对象被垃圾收集器回收之前会收到一条系统通知。

    具体实例:一种引用的get()方法返回总是null,所以,可以想象,在平常的项目开发肯定用的少。

    看到一个比较有意思的举例

    1. 强引用就像大老婆,关系很稳固。
    2. 软引用就像二老婆,随时有失宠的可能,但也有扶正的可能。
    3. 弱引用就像情人,关系不稳定,可能跟别人跑了。
    4. 幻像引用就是梦中情人,只在梦里出现过。

    知识扩展

    1.对象可达性状态流转分析

    在这里插入图片描述
    这是 Java 定义的不同可达性级别(reachability level),具体如下:

    1. 强可达(Strongly Reachable),就是当一个对象可以有一个或多个线程可以不通过各种引用访问到的情况。比如,我们新创建一个对象,那么创建它的线程对它就是强可达。
    2. 软可达(Softly Reachable),就是当我们只能通过软引用才能访问到对象的状态。
    3. 弱可达(Weakly Reachable),类似前面提到的,就是无法通过强引用或者软引用访问,只能通过弱引用访问时的状态。这是十分临近 finalize 状态的时机,当弱引用被清除的时候,就符合 finalize 的条件了。
    4. 幻象可达(Phantom Reachable),上面流程图已经很直观了,就是没有强、软、弱引用关联,并且 finalize 过了,只有幻象引用指向这个对象的时候。
    5. 当然,还有一个最后的状态,就是不可达(unreachable),意味着对象可以被清除了。

    所有引用类型,都是抽象类 java.lang.ref.Reference 的子类,你可能注意到它提供了 get() 方法:

    除了幻象引用(因为 get 永远返回 null),如果对象还没有被销毁,都可以通过 get 方法获取原有对象。这意味着,利用软引用和弱引用,可以将访问到的对象,重新指向强引用,也就是人为的改变了对象的可达性状态!这也是为什么上面图里有些地方画了双向箭头。

    所以,对于软引用、弱引用之类,垃圾收集器可能会存在二次确认的问题,以保证处于弱引用状态的对象,没有改变为强引用。

    但是,如果错误的保持了强引用(比如,赋值给了 static 变量),那么对象可能就没有机会变回类似弱引用的可达性状态了,就会产生内存泄漏。所以,检查弱引用指向对象是否被垃圾收集,也是诊断是否有特定内存泄漏的一个思路,如果我们的框架使用到弱引用又怀疑有内存泄漏,就可以从这个角度检查。

    2. 引用队列(ReferenceQueue)使用

    谈到各种引用的编程,就必然要提到引用队列。在创建各种引用并关联到响应对象时,可以选择是否需要关联引用队列,JVM 会在特定时机将引用 enqueue 到队列里,可以从队列里获取引用(remove 方法在这里实际是有获取的意思)进行相关后续逻辑。尤其是幻象引用,get 方法只返回 null,如果再不指定引用队列,基本就没有意义了。看看下面的示例代码。利用引用队列,可以在对象处于相应状态时(对于幻象引用,就是前面说的被 finalize 了,处于幻象可达状态),执行后期处理逻辑。

    Object counter = new Object();
    
    ReferenceQueue refQueue = new ReferenceQueue<>();
    
    PhantomReference<Object> p = new PhantomReference<>(counter, refQueue);
    
    counter = null;
    
    System.gc();
    
    try {
    
        // Remove 是一个阻塞方法,可以指定 timeout,或者选择一直阻塞
    
        Reference<Object> ref = refQueue.remove(1000L);
    
        if (ref != null) {
    
            // do something
    
        }
    
    } catch (InterruptedException e) {
    
        // Handle it
    
    }
    
    展开全文
  • 自我 ...此微框架的目的是为开发人员提供weakify的帮助功能,使他可以声明性地表示自己希望使用的非可选引用来封闭self ,而不必担心如何提供此引用。 用法 使用此weakify功能,以上代码将转换为
  • 今天我们浅谈一下java的四种引用,分别是强引用、软引用、弱引用与序引用。在谈引用之前我们不得不提一下Java的垃圾回收器机制GC,GC算是Java的一大特点,我们都知道c语言是不能自动释放内存的需要程序员去操作的,...

    一、前言

    今天我们浅谈一下java的四种引用,分别是强引用、软引用、弱引用与序引用。在谈引用之前我们不得不提一下Java的垃圾回收器机制GC,GC算是Java的一大特点,我们都知道c语言是不能自动释放内存的需要程序员去操作的,而Java自带的垃圾回收机制是能够帮助程序员自动释放内存的。

    所以在了解四种引用之前我们先了解一下GC

    GC:是Java自带的垃圾回收器,它是运行在独立的,优先级比较低的线程中,时刻都在检测与释放无用的内存,那是如何判断它是否有用,主要用的较多的判断方式就是引用计数

    JDK1.2版本以前:当一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及状态,程序才能使用它。简单来说:就是当我们一直在用那么它就一直有效,当我们不用了就会被回收掉,在想用就不可能了。

    JDK1.2版本之后:Java就提出了四种引用,更加方便我们去管理这些内存,同时可以让程序员通过代码的方式决定某些对象的生命周期。

    二、强引用

    1、定义

    强引用是Java中的默认类型,当一个对象如果具有强引用,只要这种引用还存在,就不会被GC回收,甚至当内存不够时,就算抛出异常也不会对其进行回收。

    2、代码

    public class Tests {
    	public static void main(String[] args) {
    		String src = new String("我是强引用");
    		System.out.println(src);
    	}
    }
    

    接下来我画一下流程的存储图 

     因为强引用不能被回收,强引用可能导致强占空间不释放,积累的多了内存泄漏会导致内存溢,所以我们为了节约内存,在我们不用了之后可以采取措施帮助GC回收。

    src=null;//帮助垃圾收集器回收此对象,这样就能预防Java虚拟机抛出OutOfMemoryError错误

     三、软引用

    1、定义

    如果一个对象具有软引用的,在JVM发生OOM之前,(即内存充足的使用)是不会被GC,只有到JVM内存不足时,才会被GC。

    2、代码

    import java.lang.ref.SoftReference;
    
    public class Tests {
    	public static void main(String[] args) {
    		String src = new String("我是强引用");
    		SoftReference<String> softRef=new SoftReference<String>(src); 
    		System.out.println(src);
    		System.out.println(softRef);
    	}
    }
    

    软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中。

    提到软引用就不能不提软引用在实际中重要的应用

    例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。

    (1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建

    (2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出

    四、弱引用

    1、定义

    如果一个对象只具有弱引用,那么这个对象就会被GC掉,被弱引用所引用的对象只能生存到下一次GC之前,当发生GC的时候无论当前内存是否足够,弱引用所引用的对象都会被GC掉。

    2、代码

    import java.lang.ref.SoftReference;
    import java.lang.ref.WeakReference;
    
    public class Tests {
    	public static void main(String[] args) {
    		String src = new String("我是强引用");
    		SoftReference<String> softRef=new SoftReference<String>(src); //软引用
    		WeakReference<String> wr = new WeakReference<String>(src);//弱引用
    //		System.out.println(src);
    //		System.out.println(softRef);
    		System.out.println(wr);
    		System.gc();
    	}
    }

    该存储主要存储不是特别重要的数据,当我们内存空间不足时,GC会及时回收防止内存溢出。

    五、虚引用

    1、定义

    “虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。简单来说其存在就是为了将关联虚引用的对象在被GC掉之后收到一个通知。

    六、总结

    1、Java四种引用级别:强引用、软引用、弱引用和虚引用。(由高到低)

    2、生存时间

    强引用:JVM停止运行时终止

    软引用:内存不足时终止

    弱引用:gc运行后终止

    虚引用:任何时候都可能

    展开全文
  • 前言 Java执行GC判断对象是否存活有两种方式其中一种是引用计数。 引用计数:Java堆中每一个对象都有一个引用计数属性,引用每新增1次计数加1...这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。 1. 强
  • Java:强引用,软引用,弱引用和虚引用

    万次阅读 多人点赞 2019-01-02 16:56:19
    三、弱引用 四、虚引用 五、总结 在JDK1.2以前的版本中,当一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及状态,程序才能使用它。这就像在商店购买了某样物品后,如果...
  • python 弱引用

    千次阅读 2019-04-21 17:40:40
    文章目录创建弱引用创建代理对象循环引用缓存对象 (WeakValueDictionary) 垃圾回收 和许多其它的高级语言一样,Python使用了垃圾回收器来自动销毁那些不再使用的对象。每个对象都有一个引用计数,当这个引用计数为...
  • 强引用:不会被回收; 软应用:内存不足时回收 弱引用:正常垃圾回收 虚引用:跟踪对象的正常垃圾回收 ...
  • > 虚引用。而GC垃圾回收器(Garbage Collection)对不同的类型有着不同的处理方法,了解这些处理方式有助于我们写出更高质量的代码。在Java中,一切被视为对象,引用则是用来操纵对象的。对...
  • Java基础篇 - 强引用、弱引用、软引用和虚引用

    万次阅读 多人点赞 2018-09-09 08:58:21
    引用计数:Java堆中每一个对象都有一个引用计数属性,引用每新增1次计数加1,引用每释放1次计数减1。 在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象...
  • c++强引用与弱引用

    2019-05-13 22:27:43
    然后用这种原始指针,其弱引用的含义不够明确,万一别人写个delete xxxx,你就被坑了……而且弱引用指针还有其它一些方便你正确使用它的好处。 作者:张景旺 链接:...
  • 1.强引用 Object obj = new Obje() 只要强引用存在,垃圾回收器就不会回收被引用的对象,当JVM内存空间不足,JVM宁愿抛出OutOFMemoryError运行时错误,使程序异常终止,也不会靠随意...弱引用是使用WeakReferenc..
  • 主要介绍了Java中强引用,软引用,弱引用概念解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • JavaScript中的弱引用和强引用

    万次阅读 2021-12-04 14:48:54
    JavaScript中的弱引用和强引用什么是弱引用和强引用举例说明弱引用和强引用在JavaScript中演示弱引用和强引用弱引用的特性总结 什么是弱引用和强引用 JavaScript 中强引用:对象的引用在 JavaScript 中是强引用,也...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 192,174
精华内容 76,869
关键字:

弱引用