精华内容
下载资源
问答
  • 理解 Java 软引用、弱引用、虚引用及其使用场景
    千次阅读
    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),但并不是所有的程序员都熟悉这两个概念
  • Java从JDK1.2版本开始,就把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用...本文给大家介绍Android利用软引用和弱引用避免OOM,需要的朋友一起学习吧
  • 主要介绍了Java弱引用(WeakReference)的理解与使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 本篇文章尝试从What、Why、How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义、基本使用场景使用方法。由于个人水平有限,叙述中难免存在不准确或是不清晰的地方,希望大家可以指出,谢谢大家:)  1....
  • 详解 JAVA 弱引用

    2020-08-18 14:19:57
    主要介绍了 JAVA 弱引用的相关资料,帮助大家更好的理解学习java引用对象,感兴趣的朋友可以了解下
  • 主要介绍了C语言中的强符号、弱符号、强引用和弱引用的定义及相关内容,非常的简单易懂,有需要的朋友可以参考下
  • java弱引用

    2018-01-18 18:05:27
    java 弱引用代码以及分析,详细讲解弱引用与强引用在垃圾回收时产生的区别
  • 将带大家快速理解Java中弱引用,文章介绍的很详细,对大家学习Java很有帮助哦,有需要的可以参考借鉴。
  • Java从JDK1.2版本开始,就把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用,本篇文章重点介绍一下软引用和弱引用
  • Lua中的弱引用介绍

    2020-09-22 05:04:32
    主要介绍了Lua中的弱引用介绍,本文用一个实例讲解了Lua弱引用的相关知识,需要的朋友可以参考下
  • 主要介绍了Android 软引用和弱引用详解的相关资料,并附实现实例代码,需要的朋友可以参考下
  • Java:强引用,软引用,弱引用和虚引用

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

    目录

    一、强引用

    二、软引用

    三、弱引用

    四、虚引用

    五、总结


    在JDK1.2以前的版本中,当一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及状态,程序才能使用它。这就像在商店购买了某样物品后,如果有用就一直保留它,否则就把它扔到垃圾箱,由清洁工人收走。一般说来,如果物品已经被扔到垃圾箱,想再把它捡回来使用就不可能了。

    但有时候情况并不这么简单,可能会遇到可有可无的"鸡肋"物品。这种物品现在已经无用了,保留它会占空间,但是立刻扔掉它也不划算,因为也许将来还会派用场。对于这样的可有可无的物品:如果家里空间足够,就先把它保留在家里,如果家里空间不够,即使把家里所有的垃圾清除,还是无法容纳那些必不可少的生活用品,那么再扔掉这些可有可无的物品。

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

    从Java SE2开始,就提供了四种类型的引用:强引用、软引用、弱引用和虚引用。Java中提供这四种引用类型主要有两个目的:第一是可以让程序员通过代码的方式决定某些对象的生命周期;第二是有利于JVM进行垃圾回收。

    一、强引用

    之前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。比如下面这段代码中的object和str都是强引用:

    Object object = new Object();
    String str = "StrongReference";

    如果一个对象具有强引用,那就类似于必不可少的物品,不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。

    public class StrongReference {
    	public static void main(String[] args) {
    		new StrongReference().method1();
    	}
    	public void method1(){
    		Object object=new Object();
    		Object[] objArr=new Object[Integer.MAX_VALUE];
    	}
    }

    运行结果:

    当运行至Object[] objArr = new Object[Integer.MAX_VALUE]时,如果内存不足,JVM会抛出OOM错误也不会回收object指向的对象。不过要注意的是,当method1运行完之后,object和objArr都已经不存在了,所以它们指向的对象都会被JVM回收。

    如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。

    比如ArraryList类的clear方法中就是通过将引用赋值为null来实现清理工作的

    public void clear() {
          modCount++;
    
          // Let gc do its work
          for (int i = 0; i < size; i++)
              elementData[i] = null;
    
          size = 0;
    }

    在ArrayList类中定义了一个私有的变量elementData数组,在调用方法清空数组时可以看到为每个数组内容赋值为null。不同于elementData=null,强引用仍然存在,避免在后续调用 add()等方法添加元素时进行重新的内存分配。使用如clear()方法中释放内存的方法对数组中存放的引用类型特别适用,这样就可以及时释放内存。

     

    二、软引用

    软引用是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。

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

    import java.lang.ref.SoftReference;
    
    public class SoftRef {  
    
        public static void main(String[] args){  
            System.out.println("start");            
            Obj obj = new Obj();            
            SoftReference<Obj> sr = new SoftReference<Obj>(obj);  
            obj = null;  
            System.out.println(sr.get());  
            System.out.println("end");     
        }       
    }  
    
    class Obj{  
        int[] obj ;  
        public Obj(){  
            obj = new int[1000];  
        }  
    }

    当内存足够大时可以把数组存入软引用,取数据时就可从内存里取数据,提高运行效率

    软引用在实际中有重要的应用,例如浏览器的后退按钮,这个后退时显示的网页内容可以重新进行请求或者从缓存中取出:

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

    (2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出这时候就可以使用软引用

     

    三、弱引用

    弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。

    弱引用与软引用的区别在于只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。所以被软引用关联的对象只有在内存不足时才会被回收,而被弱引用关联的对象在JVM进行垃圾回收时总会被回收。

    import java.lang.ref.WeakReference;
    
    public class WeakRef {
        public static void main(String[] args) {
            WeakReference<String> sr = new WeakReference<String>(new String("hello"));
            System.out.println(sr.get());
            System.gc();                //通知JVM的gc进行垃圾回收
            System.out.println(sr.get());
        }
    }

    运行结果:

    在使用软引用和弱引用的时候,我们可以显示地通过System.gc()来通知JVM进行垃圾回收,但是要注意的是,虽然发出了通知,JVM不一定会立刻执行,也就是说这句是无法确保此时JVM一定会进行垃圾回收的。

     

    弱引用还可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

    Object o = new Object(); //只要o还指向对象就不会被回收
    WeakReference<Object> wr = new WeakReference<Object>(o);

    当要获得weak reference引用的object时, 首先需要判断它是否已经被回收,如果wr.get()方法为空, 那么说明weakCar指向的对象已经被回收了。

    应用场景:如果一个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么应该用 Weak Reference 来记住此对象。或者想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候就应该用弱引用,这个引用不会在对象的垃圾回收判断中产生任何附加的影响。

     

    四、虚引用

    虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收的活动。

    虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

    import java.lang.ref.PhantomReference;
    import java.lang.ref.ReferenceQueue;
     
     
    public class PhantomRef {
        public static void main(String[] args) {
            ReferenceQueue<String> queue = new ReferenceQueue<String>();
            PhantomReference<String> pr = new PhantomReference<String>(new String("hello"), queue);
            System.out.println(pr.get());
        }
    }

     

    五、总结

    引用类型被回收时间用途生存时间
    强引用从来不会对象的一般状态JVM停止运行时
    软引用内存不足时对象缓存内存不足时
    弱引用jvm垃圾回收时对象缓存gc运行后
    虚引用未知未知未知

    在实际程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生

     

    利用软引用和弱引用解决OOM问题:假如有一个应用需要读取大量的本地图片,如果每次读取图片都从硬盘读取,则会严重影响性能,但是如果全部加载到内存当中,又有可能造成内存溢出,此时使用软引用可以解决这个问题。

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


    参考资料:

    1、https://www.cnblogs.com/dolphin0520/p/3784171.html

    2、https://blog.csdn.net/junjunba2689/article/details/80601729

    展开全文
  • referenceInJava:Java强引用,软引用,弱引用以及虚引用测试项目
  • 主要介绍了Lua教程(十三):弱引用table,一个table的弱引用类型是通过其元表的__mode字段来决定的,如果该值为包含字符"k",那么table就是key弱引用,如果包含"v",则是value弱引用,如果两个字符均存在,就是key/value...
  • 对于那些创建便宜但耗费大量内存的对象,即希望保持该对象,又要在应用程序需要时使用,同时希望GC必要时回收时,可以考虑使用弱引用弱引用使用起来很简单,看下面的代码: 代码如下: Object obj = new Object(); ...
  • Java基础篇 - 强引用、弱引用、软引用和虚引用

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

    前言

    Java执行GC判断对象是否存活有两种方式其中一种是引用计数

    引用计数:Java堆中每一个对象都有一个引用计数属性,引用每新增1次计数加1,引用每释放1次计数减1。

    JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于(reachable)可达状态,程序才能使用它。

    JDK 1.2版本开始,对象的引用被划分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用软引用弱引用虚引用

    正文

    (一) 强引用(StrongReference)

    强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。如下:

        Object strongReference = new Object();

    内存空间不足时,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用对象来解决内存不足的问题。
    如果强引用对象不使用时,需要弱化从而使GC能够回收,如下:

        strongReference = null;

    显式地设置strongReference对象为null,或让其超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于GC算法。

        public void test() {
            Object strongReference = new Object();
            // 省略其他操作
        }

    在一个方法的内部有一个强引用,这个引用保存在Java中,而真正的引用内容(Object)保存在Java中。
    当这个方法运行完成后,就会退出方法栈,则引用对象的引用数0,这个对象会被回收。

    但是如果这个strongReference全局变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收。

    ArrayList的Clear方法:

    ArrayList类中定义了一个elementData数组,在调用clear方法清空数组时,每个数组元素被赋值为null
    不同于elementData=null,强引用仍然存在,避免在后续调用add()等方法添加元素时进行内存的重新分配
    使用如clear()方法内存数组中存放的引用类型进行内存释放特别适用,这样就可以及时释放内存。

    (二) 软引用(SoftReference)

    如果一个对象只具有软引用,则内存空间充足时,垃圾回收器不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

    软引用可用来实现内存敏感的高速缓存。

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

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

        ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
        String str = new String("abc");
        SoftReference<String> softReference = new SoftReference<>(str, referenceQueue);
    
        str = null;
        // Notify GC
        System.gc();
    
        System.out.println(softReference.get()); // abc
    
        Reference<? extends String> reference = referenceQueue.poll();
        System.out.println(reference); //null

    注意:软引用对象是在jvm内存不够的时候才会被回收,我们调用System.gc()方法只是起通知作用,JVM什么时候扫描回收对象是JVM自己的状态决定的。就算扫描到软引用对象也不一定会回收它,只有内存不够的时候才会回收。

    当内存不足时,JVM首先将软引用中的对象引用置为null,然后通知垃圾回收器进行回收:

        if(JVM内存不足) {
            // 将软引用中的对象引用置为null
            str = null;
            // 通知垃圾回收器进行回收
            System.gc();
        }

    也就是说,垃圾收集线程会在虚拟机抛出OutOfMemoryError之前回收软引用对象,而且虚拟机会尽可能优先回收长时间闲置不用软引用对象。对那些刚构建的或刚使用过的“较新的”软对象会被虚拟机尽可能保留,这就是引入引用队列ReferenceQueue的原因。

    应用场景:

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

    1. 如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建;
    2. 如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出。

    这时候就可以使用软引用,很好的解决了实际的问题:

        // 获取浏览器对象进行浏览
        Browser browser = new Browser();
        // 从后台程序加载浏览页面
        BrowserPage page = browser.getPage();
        // 将浏览完毕的页面置为软引用
        SoftReference softReference = new SoftReference(page);
    
        // 回退或者再次浏览此页面时
        if(softReference.get() != null) {
            // 内存充足,还没有被回收器回收,直接获取缓存
            page = softReference.get();
        } else {
            // 内存不足,软引用的对象已经回收
            page = browser.getPage();
            // 重新构建软引用
            softReference = new SoftReference(page);
        }

    (三) 弱引用(WeakReference)

    弱引用软引用的区别在于:只具有弱引用的对象拥有更短暂生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定很快发现那些只具有弱引用的对象。

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

    JVM首先将软引用中的对象引用置为null,然后通知垃圾回收器进行回收:

        str = null;
        System.gc();

    注意:如果一个对象是偶尔(很少)的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用Weak Reference来记住此对象。

    下面的代码会让一个弱引用再次变为一个强引用

        String str = new String("abc");
        WeakReference<String> weakReference = new WeakReference<>(str);
        // 弱引用转强引用
        String strongReference = weakReference.get();

    同样,弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象垃圾回收Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

    简单测试:

    GCTarget.java

    public class GCTarget {
        // 对象的ID
        public String id;
    
        // 占用内存空间
        byte[] buffer = new byte[1024];
    
        public GCTarget(String id) {
            this.id = id;
        }
    
        protected void finalize() throws Throwable {
            // 执行垃圾回收时打印显示对象ID
            System.out.println("Finalizing GCTarget, id is : " + id);
        }
    }

    GCTargetWeakReference.java

    public class GCTargetWeakReference extends WeakReference<GCTarget> {
        // 弱引用的ID
        public String id;
    
        public GCTargetWeakReference(GCTarget gcTarget,
                  ReferenceQueue<? super GCTarget> queue) {
            super(gcTarget, queue);
            this.id = gcTarget.id;
        }
    
        protected void finalize() {
            System.out.println("Finalizing GCTargetWeakReference " + id);
        }
    }

    WeakReferenceTest.java

    public class WeakReferenceTest {
        // 弱引用队列
        private final static ReferenceQueue<GCTarget> REFERENCE_QUEUE = new ReferenceQueue<>();
    
        public static void main(String[] args) {
            LinkedList<GCTargetWeakReference> gcTargetList = new LinkedList<>();
    
            // 创建弱引用的对象,依次加入链表中
            for (int i = 0; i < 5; i++) {
                GCTarget gcTarget = new GCTarget(String.valueOf(i));
                GCTargetWeakReference weakReference = new GCTargetWeakReference(gcTarget,
                    REFERENCE_QUEUE);
                gcTargetList.add(weakReference);
    
                System.out.println("Just created GCTargetWeakReference obj: " +
                    gcTargetList.getLast());
            }
    
            // 通知GC进行垃圾回收
            System.gc();
    
            try {
                // 休息几分钟,等待上面的垃圾回收线程运行完成
                Thread.sleep(6000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            // 检查关联的引用队列是否为空
            Reference<? extends GCTarget> reference;
            while((reference = REFERENCE_QUEUE.poll()) != null) {
                if(reference instanceof GCTargetWeakReference) {
                    System.out.println("In queue, id is: " +
                        ((GCTargetWeakReference) (reference)).id);
                }
            }
        }
    }

    运行WeakReferenceTest.java,运行结果如下:

    可见WeakReference对象的生命周期基本由垃圾回收器决定,一旦垃圾回收线程发现了弱引用对象,在下一次GC过程中就会对其进行回收。

    (四) 虚引用(PhantomReference)

    虚引用顾名思义,就是形同虚设。与其他几种引用都不同,虚引用不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

    应用场景:

    虚引用主要用来跟踪对象被垃圾回收器回收的活动。
    虚引用软引用弱引用的一个区别在于:

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

        String str = new String("abc");
        ReferenceQueue queue = new ReferenceQueue();
        // 创建虚引用,要求必须与一个引用队列关联
        PhantomReference pr = new PhantomReference(str, queue);

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

    总结

    Java中4种引用的级别和强度由高到低依次为:强引用 -> 软引用 -> 弱引用 -> 虚引用

    垃圾回收器回收时,某些对象会被回收,某些不会被回收。垃圾回收器会从根对象Object标记存活的对象,然后将某些不可达的对象和一些引用的对象进行回收。

    通过表格来说明一下,如下:

    引用类型被垃圾回收时间用途生存时间
    强引用从来不会对象的一般状态JVM停止运行时终止
    软引用当内存不足时对象缓存内存不足时终止
    弱引用正常垃圾回收时对象缓存垃圾回收后终止
    虚引用正常垃圾回收时跟踪对象的垃圾回收垃圾回收后终止

    欢迎关注技术公众号: 零壹技术栈

    image

    本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。

    展开全文
  • 1 Java引用介绍 Java从1.2版本开始引入了4种引用,这4种引用的级别由高到低依次为: 强引用 >... 弱引用 > 虚引用 ⑴强引用(StrongReference) 强引用是使用最普遍的引用。如果一个对象具...

    本文章转载自 https://www.cnblogs.com/skywang12345/p/3154474.html

    1 Java引用介绍

       Java从1.2版本开始引入了4种引用,这4种引用的级别由高到低依次为:

       强引用  >  软引用  >  弱引用  >  虚引用

    ⑴强引用(StrongReference)
        强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

    ⑵软引用(SoftReference)

        如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

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

    ⑶弱引用(WeakReference)

        弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

        弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

    ⑷虚引用(PhantomReference)

        “虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

        虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

     

    由于引用和内存回收关系紧密。下面,先通过实例对内存回收有个认识;然后,进一步通过引用实例加深对引用的了解。

     

     

     


    2 内存回收

    创建公共类MyDate,它的作用是覆盖finalize()函数:在finalize()中输出打印信息,方便追踪。

    说明:finalize()函数是在JVM回收内存时执行的,但JVM并不保证在回收内存时一定会调用finalize()。

    MyDate代码如下:

    复制代码

    package com.skywang.java;
    
    import java.util.Date;
    
    public class MyDate extends Date { 
    
        /** Creates a new instance of MyDate */
        public MyDate() {
        }
        // 覆盖finalize()方法
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("obj [Date: " + this.getTime() + "] is gc");
        }   
    
        public String toString() {
            return "Date: " + this.getTime();
        }
    }

    复制代码

    在这个类中,对java.util.Date类进行了扩展,并重写了finalize()和toString()方法。

     

    创建公共类ReferenceTest,它的作用是定义一个方法drainMemory():消耗大量内存,以此来引发JVM回收内存。

    ReferenceTest代码如下:

    复制代码

    package com.skywang.java;
    
    public class ReferenceTest {   
        /** Creates a new instance of ReferenceTest */
        public ReferenceTest() {
        }   
        
        // 消耗大量内存
        public static void drainMemory() {
            String[] array = new String[1024 * 10];
            for(int i = 0; i < 1024 * 10; i++) {
                for(int j = 'a'; j <= 'z'; j++) {
                    array[i] += (char)j;
                }           
            }
        }
    } 

    复制代码

    在这个类中定义了一个静态方法drainMemory(),此方法旨在消耗大量的内存,促使JVM运行垃圾回收。

     

    有了上面两个公共类之后,我们即可测试JVM什么时候进行垃圾回收。下面分3种情况进行测试:

    情况1:清除对象

    实现代码

    复制代码

    package com.skywang.java;
    
    public class NoGarbageRetrieve {
    
        public static void main(String[] args) {
            MyDate date = new MyDate();
            date = null;
        }
    }

    复制代码

    运行结果

    <无任何输出>

    结果分析:date虽然设为null,但由于JVM没有执行垃圾回收操作,MyDate的finalize()方法没有被运行。

     

    情况2:显式调用垃圾回收

    实现代码: 

    复制代码

    package com.skywang.java;
    
    public class ExplicitGarbageRetrieve {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            MyDate date = new MyDate();
            date = null;
            System.gc();
        }
    
    }

    复制代码

    运行结果

    obj [Date: 1372137067328] is gc

    结果分析:调用了System.gc(),使JVM运行垃圾回收,MyDate的finalize()方法被运行。

     

    情况3:隐式调用垃圾回收

    实现代码: 

    复制代码

    package com.skywang.java;
    
    public class ImplicitGarbageRetrieve {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            MyDate date = new MyDate();
            date = null;
            ReferenceTest.drainMemory();
        }
    
    } 

    复制代码

    运行结果

    obj [Date: 1372137171965] is gc

    结果分析:虽然没有显式调用垃圾回收方法System.gc(),但是由于运行了耗费大量内存的方法,触发JVM进行垃圾回收。

     

    总结:JVM的垃圾回收机制,在内存充足的情况下,除非你显式调用System.gc(),否则它不会进行垃圾回收;在内存不足的情况下,垃圾回收将自动运行

     

     

     


    3、Java对引用的分类

    3.1 强引用

    实例代码

    复制代码

    package com.skywang.java;
    
    public class StrongReferenceTest {
    
        public static void main(String[] args) {
            MyDate date = new MyDate();
            System.gc();
        }
    }

    复制代码

    运行结果

    <无任何输出>

    结果说明:即使显式调用了垃圾回收,但是用于date是强引用,date没有被回收。

     

    3.2 软引用

    实例代码

    复制代码

    package com.skywang.java;
    
    import java.lang.ref.SoftReference;
    
    public class SoftReferenceTest {
    
        public static void main(String[] args) {
            SoftReference ref = new SoftReference(new MyDate());
            ReferenceTest.drainMemory();
        }
    }

    复制代码

    运行结果

    <无任何输出>

    结果说明:在内存不足时,软引用被终止。软引用被禁止时,

    SoftReference ref = new SoftReference(new MyDate());
    ReferenceTest.drainMemory();

    等价于

    MyDate date = new MyDate();

    // 由JVM决定运行
    If(JVM.内存不足()) {
    date = null;
    System.gc();
    }

     

    3.3 弱引用

    示例代码: 

    复制代码

    package com.skywang.java;
    
    import java.lang.ref.WeakReference;
    
    public class WeakReferenceTest {
    
        public static void main(String[] args) {
            WeakReference ref = new WeakReference(new MyDate());
            System.gc(); 
        }
    }

    复制代码

    运行结果

    obj [Date: 1372142034360] is gc

    结果说明:在JVM垃圾回收运行时,弱引用被终止.

    WeakReference ref = new WeakReference(new MyDate());
    System.gc();

    等同于:

    MyDate date = new MyDate();

    // 垃圾回收
    If(JVM.内存不足()) {
    date = null;
    System.gc();
    }

     

    3. 4 假象引用

    示例代码: 

    复制代码

    package com.skywang.java;
    
    import java.lang.ref.ReferenceQueue;
    import java.lang.ref.PhantomReference;
    
    public class PhantomReferenceTest {
    
        public static void main(String[] args) {
            ReferenceQueue queue = new ReferenceQueue();
            PhantomReference ref = new PhantomReference(new MyDate(), queue);
            System.gc();
        }
    }

    复制代码

    运行结果

    obj [Date: 1372142282558] is gc

    结果说明:假象引用,在实例化后,就被终止了。

    ReferenceQueue queue = new ReferenceQueue();
    PhantomReference ref = new PhantomReference(new MyDate(), queue);
    System.gc();

    等同于:

    MyDate date = new MyDate();
    date = null;

     

     

    可以用以下表格总结上面的内容: 

    级别

    什么时候被垃圾回收

    用途

    生存时间

    强引用

    从来不会

    对象的一般状态

    JVM停止运行时终止

    软引用

    在内存不足时

    对象简单?缓存

    内存不足时终止

    弱引用

    在垃圾回收时

    对象缓存

    gc运行后终止

    虚引用

    Unknown

    Unknown

    Unknown

     

     

     

    展开全文
  • 软引用、弱引用和虚引用处理

    千次阅读 2017-12-27 00:18:19
    从而认识了弱引用、软引用、虚引用。今天发现Kotlin 在Android 上Anko库里的async, uiThread 里面居然做了在异步执行过程中Activity销毁了uiThread则不会调用,防止内存泄漏。正是采用了弱引用,先温习一下。Java中...
  • 主要介绍了深入理解Java中的弱引用,本文讲解了强引用、弱引用、引用队列、四种引用、软引用、虚引用等内容,需要的朋友可以参考下
  • 最近看到swift里面不仅有循环引用和弱引用(weak),还添加了主引用(unowned),于是写了一些demo,这里总结一下。和OC一样,Swfit默认也是基于ARC进行内存管理的,因此虽然简单,但如果不注意任然会出现循环引用...
  • C#中的强引用和弱引用

    千次阅读 2018-07-17 21:55:40
    我们平常用的都是对象的强引用,如果有强引用存在,GC是不会回收对象...对于那些创建便宜但耗费大量内存的对象,即希望保持该对象,又要在应用程序需要时使用,同时希望GC必要时回收时,可以考虑使用弱引用。 一:...
  • Java 如何有效地避免OOM:善于利用软引用和弱引用。  想必很多朋友对OOM(OutOfMemory)这个错误不会陌生,而当遇到这种错误如何有效地解决这个问题呢?今天我们就来说一下如何利用软引用和弱引用来有效地解决程序...
  • 引用和弱引用的应用场景

    千次阅读 2019-03-05 22:55:41
    引用和弱引用 SoftReference(软引用)和WeakReference(弱引用)在JAVA中对应着两个类,在SoftReference类中,有三个方法,两个构造方法和一个get方法(WekReference类似),而当应用它们指向对象时应该如下操作 ...
  • 旭日Follow_24 的CSDN 博客 ,全文地址请点击:一、强引用如下是强引用的经典形式:object o = new object();特点:(1)创建一个对象,并将对这个对象的引用赋值给o,这样就是强引用了(2)当内存空间不足的时候,...
  • JavaScript中的弱引用和强引用

    万次阅读 2021-12-04 14:48:54
    JavaScript中的弱引用和强引用什么是弱引用和强引用举例说明弱引用和强引用在...JavaScript 中弱引用: WeakMaps WeakSets 是我们在 JavaScript 使用弱引用唯一途径,将一个对象作为键添加到 WeakMap 或 We
  • 1、强引用(StrongReference) 最普遍的一种引用方式,如String s = “abc”,变量s就是字符串“abc”的强引用,只要强引用...一般用于实现内存敏感的高速缓存,软引用可以和引用队列ReferenceQueue联合使用,如果...
  • 1.强引用 我们使用的大部分的引用都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,...
  • 引用大佬的博客: Java:强引用,软引用,弱引用和虚引用 建议直接看大佬的博客,我这里只做总结。 总结 强引用 相当于 Object obj=new Object()这种引用就是强引用,即使OOM也不会被垃圾回收器进行回收 软引用 如果...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 185,351
精华内容 74,140
关键字:

弱引用和无引用