深入了解JAVA 软引用

定义

软引用是使用SoftReference创建的引用,强度弱于强引用,被其引用的对象在内存不足的时候会被回收,不会产生内存溢出。

说明

软引用,顾名思义就是比较“软”一点的引用。

当一个对象与GC Roots之间存在强引用时,无论何时都不会被GC回收掉。如果一个对象与GC Roots之间没有强引用与其关联而存在软引用关联时,那么垃圾回收器对它的态度就取决于内存的紧张程度了。如果内存空间足够,垃圾回收器就不会回收这个对象,但如果内存空间不足了,它就难逃被回收的厄运。

如果一个对象与GC Roots之间不存在强引用,但是存在软引用,则称这个对象为软可达(soft reachable)对象。

在垃圾回收器没有回收它的时候,软可达对象就像强可达对象一样,可以被程序正常访问和使用,但是需要通过软引用对象间接访问,需要的话也能重新使用强引用将其关联。所以软引用适合用来做内存敏感的高速缓存。

String s = new String("Frank");  // 创建强引用与String对象关联,现在该String对象为强可达状态
SoftReference<String> softRef = new SoftReference<String>(s);   // 再创建一个软引用关联该对象
s = null;    // 消除强引用,现在只剩下软引用与其关联,该String对象为软可达状态
s = softRef.get(); // 重新关联上强引用

这里变量s持有对字符串对象的强引用,而softRef持有对该对象的软引用,所以当执行s = null后,字符串对象就只剩下软引用了,这时如果因为内存不足发生Full GC,就会把这个字符串对象回收掉。

注意,在垃圾回收器回收一个对象前,SoftReference类所提供的get方法会返回Java对象的强引用,一旦垃圾线程回收该对象之后,get方法将返回null。所以在获取软引用对象的代码中,一定要先判断返回是否为null,以免出现NullPointerException异常而导致应用崩溃。

下面的代码会让s再次持有对象的强引用:

s = softRef.get();

如果在softRef指向的对象被回收前,用强引用指向该对象,那这个对象又会变成强可达。

来看一个使用SoftReference的栗子:

public class TestA {
  static class OOMClass{
    private int[] oom = new int[1024 * 100];// 100KB
  }

  public static void main(String[] args) throws InterruptedException {
    ReferenceQueue<OOMClass> queue = new ReferenceQueue<>();
    List<SoftReference> list = new ArrayList<>();
    while(true){
      for (int i = 0; i < 100; i++) {
        list.add(new SoftReference<OOMClass>(new OOMClass(), queue));
      }
      Thread.sleep(500);
    }
  }
}

注意,ReferenceQueue中声明的类型为OOMClass,即与SoftReference引用的类型一致。

设置一下虚拟机参数:

-verbose:gc -Xms4m -Xmx4m -Xmn2m

运行结果:

[GC (Allocation Failure) 1017K->432K(3584K), 0.0017239 secs]
[GC (Allocation Failure) 1072K->472K(3584K), 0.0099237 secs]
[GC (Allocation Failure) 1323K->1296K(3584K), 0.0009528 secs]
[GC (Allocation Failure) 2114K->2136K(3584K), 0.0009951 secs]
[Full GC (Ergonomics) 2136K->1992K(3584K), 0.0040658 secs]
[Full GC (Ergonomics) 2807K->2791K(3584K), 0.0036280 secs]
[Full GC (Allocation Failure) 2791K->373K(3584K), 0.0032477 secs]
[Full GC (Ergonomics) 2786K->2773K(3584K), 0.0034554 secs]
[Full GC (Allocation Failure) 2773K->373K(3584K), 0.0032667 secs]
[Full GC (Ergonomics) 2798K->2775K(3584K), 0.0036231 secs]
[Full GC (Allocation Failure) 2775K->375K(3584K), 0.0055482 secs]
[Full GC (Ergonomics) 2799K->2776K(3584K), 0.0031358 secs]
...省略n次GC信息

在TestA中,我们使用死循环不断的往list中添加新对象,如果是强引用,会很快因为内存不足而抛出OOM,因为这里的堆内存大小设置为了4M,而一个对象就有100KB,一个循环添加100个对象,也就是差不多10M,显然一个循环都跑不完就会内存不足,而这里,因为使用的是软引用,所以JVM会在内存不足的时候将软引用回收掉。

[Full GC (Allocation Failure) 2791K->373K(3584K), 0.0032477 secs]

从这一条可以看出,在内存不足发生Full GC时,回收掉了大部分的软引用指向的对象,释放了大量的内存。

因为这里新生代只分配了2M,所以很快就会发生GC,如果你的程序运行没有看到这个结果,请先确认一下虚拟机参数是否设置正确,如果设置正确还是没有看到,那么将循环次数由1000改为10000或者100000在试试看。

应用场景

软引用关联的对象,只有在内存不足的时候JVM才会回收该对象。这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。

现在考虑这样一个场景 ,在很多应用中,都会出现大量的默认图片,比如说QQ的默认头像,应用内的默认图标等等,这些图片很多地方会用到。

如果每次都去读取图片,由于读取文件速度较慢,大量重复的读取会导致性能下降。所以可以考虑将图片缓存起来,需要的时候直接从内存中读取。但是,由于图片占用内存空间比较大,缓存的图片过多会占用比较多的内存,就可能比较容易发生OOM。这时候,软引用就派得上用场了。

注意,SoftReference对象是用来保存软引用的,但它同时也是一个Java对象。所以,当软可及对象被回收之后,虽然这个SoftReference对象的get()方法返回null,但SoftReference对象本身并不是null,而此时这个SoftReference对象已经不再具有存在的价值,需要一个适当的清除机制,避免大量SoftReference对象带来的内存泄漏。

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

下面用SoftReference来实现一个简单的缓存类:

public class SoftCache<T> {
  // 引用队列
  private ReferenceQueue<T> referenceQueue = new ReferenceQueue<>();
  // 保存软引用集合,在引用对象被回收后销毁
  private List<Reference<T>> list = new ArrayList<>();

  // 添加缓存对象
  public synchronized void add(T obj){
    // 构建软引用
    Reference<T> reference = new SoftReference<T>(obj, referenceQueue);
    // 加入列表中
    list.add(reference);
  }

  // 获取缓存对象
  public synchronized T get(int index){
    // 先对无效引用进行清理
    clear();
    if (index < 0 || list.size() < index){
      return null;
    }
    Reference<T> reference = list.get(index);
    return reference == null ? null : reference.get();
  }

  public int size(){
    return list.size();
  }

  @SuppressWarnings("unchecked")
  private void clear(){
    Reference<T> reference;
    while (null != (reference = (Reference<T>) referenceQueue.poll())){
      list.remove(reference);
    }
  }
}

然后测试一下这个缓存类:

public class SoftCacheTest {
  private static int num = 0;

  public static void main(String[] args){
    SoftCache<OOMClass> softCache = new SoftCache<>();
    for (int i = 0; i < 40; i++) {
      softCache.add(new OOMClass("OOM Obj-" + ++num));
    }
    System.out.println(softCache.size());
    for (int i = 0; i < softCache.size(); i++) {
      OOMClass obj = softCache.get(i);
      System.out.println(obj == null ? "null" : obj.name);
    }
    System.out.println(softCache.size());
  }

  static class OOMClass{
    private String name;
    private int[] oom = new int[1024 * 100];// 100KB

    public OOMClass(String name) {
      this.name = name;
    }
  }
}

仍使用之前的虚拟机参数:

-verbose:gc -Xms4m -Xmx4m -Xmn2m

运行结果:

[GC (Allocation Failure) 1017K->432K(3584K), 0.0012236 secs]
[GC (Allocation Failure) 1117K->496K(3584K), 0.0016875 secs]
[GC (Allocation Failure) 1347K->1229K(3584K), 0.0015059 secs]
[GC (Allocation Failure) 2047K->2125K(3584K), 0.0018090 secs]
[Full GC (Ergonomics) 2125K->1994K(3584K), 0.0054759 secs]
[Full GC (Ergonomics) 2822K->2794K(3584K), 0.0023167 secs]
[Full GC (Allocation Failure) 2794K->376K(3584K), 0.0036056 secs]
[Full GC (Ergonomics) 2795K->2776K(3584K), 0.0042365 secs]
[Full GC (Allocation Failure) 2776K->376K(3584K), 0.0035122 secs]
[Full GC (Ergonomics) 2795K->2776K(3584K), 0.0054760 secs]
[Full GC (Allocation Failure) 2776K->376K(3584K), 0.0036965 secs]
[Full GC (Ergonomics) 2802K->2777K(3584K), 0.0044513 secs]
[Full GC (Allocation Failure) 2777K->376K(3584K), 0.0041400 secs]
[Full GC (Ergonomics) 2796K->2777K(3584K), 0.0025255 secs]
[Full GC (Allocation Failure) 2777K->376K(3584K), 0.0037690 secs]
[Full GC (Ergonomics) 2817K->2777K(3584K), 0.0037759 secs]
[Full GC (Allocation Failure) 2777K->377K(3584K), 0.0042416 secs]
缓存列表大小:40
OOM Obj-37
OOM Obj-38
OOM Obj-39
OOM Obj-40
缓存列表大小:4

可以看到,缓存40个软引用对象之后,如果一次性全部存储,显然内存大小无法满足,所以在不断创建软引用对象的过程中,不断发生GC来进行垃圾回收,最终只有4个软引用未被清理掉。

强引用与软引用对比

没有对比就没有伤害,来将强引用和软引用对比一下:

public class Test {

  static class OOMClass{
    private int[] oom = new int[1024];
  }

  public static void main(String[] args) {
    testStrongReference();
    //testSoftReference();
  }

  public static void testStrongReference(){
    List<OOMClass> list = new ArrayList<>();
    for (int i = 0; i < 1000; i++) {
      list.add(new OOMClass());
    }
  }

  public static void testSoftReference(){
    ReferenceQueue<OOMClass> referenceQueue = new ReferenceQueue<>();
    List<SoftReference> list = new ArrayList<>();
    for (int i = 0; i < 1000; i++) {
      OOMClass oomClass = new OOMClass();
      list.add(new SoftReference(oomClass, referenceQueue));
      oomClass = null;
    }
  }
}

运行testStrongReference方法的结果如下:

[GC (Allocation Failure) 1019K->384K(3584K), 0.0033595 secs]
[GC (Allocation Failure) 1406K->856K(3584K), 0.0013098 secs]
[GC (Allocation Failure) 1880K->1836K(3584K), 0.0014382 secs]
[Full GC (Ergonomics) 1836K->1756K(3584K), 0.0039761 secs]
[Full GC (Ergonomics) 2778K->2758K(3584K), 0.0021269 secs]
[Full GC (Ergonomics) 2779K->2770K(3584K), 0.0016329 secs]
[Full GC (Ergonomics) 2779K->2775K(3584K), 0.0023157 secs]
[Full GC (Ergonomics) 2775K->2775K(3584K), 0.0015927 secs]
[Full GC (Ergonomics) 3037K->3029K(3584K), 0.0025071 secs]
[Full GC (Ergonomics) 3067K->3065K(3584K), 0.0017529 secs]
[Full GC (Allocation Failure) 3065K->3047K(3584K), 0.0033445 secs]
[Full GC (Ergonomics) 3068K->3059K(3584K), 0.0016623 secs]
[Full GC (Ergonomics) 3070K->3068K(3584K), 0.0028357 secs]
[Full GC (Allocation Failure) 3068K->3068K(3584K), 0.0017616 secs]
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid3352.hprof ...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Heap dump file created [3855956 bytes in 0.017 secs]
[Full GC (Ergonomics) 3071K->376K(3584K), 0.0032068 secs]
at reference.Test$OOMClass.<init>(Test.java:11)
at reference.Test.testStrongReference(Test.java:22)
at reference.Test.main(Test.java:15)

Process finished with exit code 1

可以看到,很快就抛出了OOM,原因是Java heap space,也就是堆内存不足。

如果运行testSoftReference方法,将会得到如下结果:

[GC (Allocation Failure) 1019K->464K(3584K), 0.0019850 secs]
[GC (Allocation Failure) 1484K->844K(3584K), 0.0015920 secs]
[GC (Allocation Failure) 1868K->1860K(3584K), 0.0043236 secs]
[Full GC (Ergonomics) 1860K->1781K(3584K), 0.0044581 secs]
[Full GC (Ergonomics) 2802K->2754K(3584K), 0.0041726 secs]
[Full GC (Ergonomics) 2802K->2799K(3584K), 0.0031293 secs]
[Full GC (Ergonomics) 3023K->3023K(3584K), 0.0024830 secs]
[Full GC (Ergonomics) 3071K->3068K(3584K), 0.0035025 secs]
[Full GC (Allocation Failure) 3068K->405K(3584K), 0.0040672 secs]
[GC (Allocation Failure) 1512K->1567K(3584K), 0.0011170 secs]
[Full GC (Ergonomics) 1567K->1496K(3584K), 0.0048438 secs]

可以看到,并没有抛出OOM,而是进行多次了GC,可以明显的看到这一条:

[Full GC (Allocation Failure) 3068K->405K(3584K), 0.0040672 secs]

当内存不足时进行了一次Full GC,回收了大部分内存空间,也就是将大部分软引用指向的对象回收掉了。

小结

  • 软引用弱于强引用
  • 软引用指向的对象会在内存不足时被垃圾回收清理掉
  • JVM会优先回收长时间闲置不用的软引用对象,对那些刚刚构建的或刚刚使用过的软引用对象会尽可能保留
  • 软引用可以有效的解决OOM问题
  • 软引用适合用作非必须大对象的缓存

至此,本篇就告一段落了,这里只简单的介绍了软引用的作用以及用法。其实软引用并没有这么好,它的使用有一些可能是致命的缺点,如果想要更深入的了解软引用的运行原理以及软引用到底是在何时进行回收,又是如何进行回收的话,可以查看翻阅后续的章节。

以上就是深入了解JAVA 软引用的详细内容,更多关于JAVA 软引用的资料请关注我们其它相关文章!

(0)

相关推荐

  • 详解JAVA 弱引用

    定义 弱引用是使用WeakReference创建的引用,弱引用也是用来描述非必需对象的,它是比软引用更弱的引用类型.在发生GC时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收. 说明 弱引用,从名字来看就很弱嘛,这种引用指向的对象,一旦在GC时被扫描到,就逃脱不了被回收的命运. 但是,弱引用指向的对象也并不一定就马上会被回收,如果弱引用对象较大,直接进到了老年代,那么就可以苟且偷生到Full GC触发前,所以弱引用对象也可能存在较长的一段时间.一旦一个弱引用对象被垃圾回收器回收,便

  • 详解JAVA 虚引用

    定义 虚引用是使用PhantomReference创建的引用,虚引用也称为幽灵引用或者幻影引用,是所有引用类型中最弱的一个.一个对象是否有虚引用的存在,完全不会对其生命周期构成影响,也无法通过虚引用获得一个对象实例. 说明 虚引用,正如其名,对一个对象而言,这个引用形同虚设,有和没有一样. 如果一个对象与GC Roots之间仅存在虚引用,则称这个对象为虚可达(phantom reachable)对象. 当试图通过虚引用的get()方法取得强引用时,总是会返回null,并且,虚引用必须和引用队列一

  • Java中弱引用和软引用的区别以及虚引用和强引用介绍

    知道弱引用和软引用的概念与如何使用它们是两码事,引用类在垃圾回收工作的过程中有重要作用.我们都知道垃圾回收器会回收符合回收条件的对象的内存,但并不是所有的程序员都知道回收条件取决于指向该对象的引用类型.这正是Java中弱引用和软引用的主要区别.如果一个对象只有弱引用指向它,垃圾回收器会立即回收该对象,这是一种急切回收方式.相对的,如果有软引用指向这些对象,则只有在JVM需要内存时才回收这些对象.弱引用和软引用的特殊行为使得它们在某些情况下非常有用.例如:软引用可以很好的用来实现缓存,当JVM需要

  • Java中强引用,软引用,弱引用概念解析

    1.概念解释强引用是使用最普遍的引用:Object o=new Object(); 特点:不会被GC 将对象的引用显示地置为null:o=null; // 帮助垃圾收集器回收此对象 举例ArrayList的实现源代码: &amp;lt;img src="https://pic2.zhimg.com/50/dd6f826c4e0c045f3701978f311636e1_hd.png" data-rawwidth="361" data-rawheight=&q

  • Java中值类型和引用类型的比较与问题解决

    一.问题描述 前几天因为一个需求出现了Bug.说高级点也挺高级,说白点也很简单.其实也就是一个很简单的Java基础入门时候的值类型和引用类型的区别.只是开发的时候由于自己的问题,导致小问题的出现.还好突然想起来以前看过一篇对于该问题讲解的博客,才能快速定位问题的位置.防止下次再犯,顺便也就把这个当做笔记记录下来,放入自己的Bug集中. 二.值类型和引用类型的比较 这个大家应该都是没问题的,很简单.值类型比较是比较值,引用类型是比较地址.对于正常的操作来说,比较值类型我们可以直接使用 == ,引用

  • java通过实例了解值传递和引用传递

    这篇文章主要介绍了java通过实例了解值传递和引用传递,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.java中的值传递的问题 指的是在方法调用时,传递的参数是按值的拷贝传递.示例如下: public static void main(String[] args) { int a=1; change(a); System.out.println("交换a后的值:"+a); } private static void change(

  • 基于java读取并引用自定义配置文件

    首先在resources目录创建自定义的配置文件 配置文件的格式: 写工具类,得到配置参数 import java.io.IOException; import java.io.InputStream; import java.util.Properties; public class MyConfig { public static Properties myProp = new Properties(); public static InputStream myResource = MyCo

  • 详解JAVA 强引用

    定义 强引用是使用最普遍的引用.如果一个对象具有强引用,那垃圾回收器宁愿抛出OOM(OutOfMemoryError)也不会回收它. 说明 不要被这个强字吓到,以为这个引用就很厉害,其实强引用就是程序中使用的一般引用类型.举个简单的栗子: String s = new String("Hello Frank!"); 强可达 如果一个对象与GC Roots之间存在强引用,则称这个对象为强可达(strong reachable)对象. 当你声明一个变量并指向一个实例的时候,其实就是在创造一

  • 深入了解JAVA 软引用

    定义 软引用是使用SoftReference创建的引用,强度弱于强引用,被其引用的对象在内存不足的时候会被回收,不会产生内存溢出. 说明 软引用,顾名思义就是比较"软"一点的引用. 当一个对象与GC Roots之间存在强引用时,无论何时都不会被GC回收掉.如果一个对象与GC Roots之间没有强引用与其关联而存在软引用关联时,那么垃圾回收器对它的态度就取决于内存的紧张程度了.如果内存空间足够,垃圾回收器就不会回收这个对象,但如果内存空间不足了,它就难逃被回收的厄运. 如果一个对象与GC

  • java软引用在浏览器使用实例讲解

    1.说明 如果一个网页在浏览结束时回收内容,需要按后退查看之前浏览过的页面时重建: 如果将浏览过的网页存储在内存中,会造成大量内存浪费,甚至导致内存溢出. 2.实例 // 获取浏览器对象进行浏览 Browser browser = new Browser(); // 从后台程序加载浏览页面 BrowserPage page = browser.getPage(); // 将浏览完毕的页面置为软引用 SoftReference softReference = new SoftReference(p

  • java使用软引用实现缓存机制示例

    目录 正文 软引用和强引用 项目 使用idea创建一个maven项目 首先对Good实体类进行编写. 然后我们在goodbase里面编写代码,模拟一个数据库 然后书写goodscache缓存类 goodsservice模拟数据库增删改查 最后我们书写test文件 运行结果 正文 “读多写少”是大部分项目的一个特点.例如“购物”,总是看的人多(读).买的人少(写).因此,如果能减少“读”请求的次数,就能减少服务端的压力.最直接的减少“读”请求次数的方法就是使用缓存. 软引用和强引用 对于同一个读请

  • Java中的强引用,软引用,弱引用,虚引用的作用介绍

    目录 1.强引用(StrongReference) 2.软引用(SoftReference) 3.弱引用(WeakReference) 4.虚引用(PhantomReference) 5.强引用>软引用>弱引用>虚引用 1.强引用( Strong Reference ) 最普遍的引用:Object obj=new Object()抛出OutOfMemoryError终止程序也不会回收具有强引用的对象通过将对象设置为null来弱化引用,使其被回收 2.软引用( Soft Reference

  • 解析Android开发优化之:软引用与弱引用的应用

    如果一个对象只具有软引用,那么如果内存空间足够,垃圾回收器就不会回收它:如果内存空间不足了,就会回收这些对象的内存.只要垃圾回收器没有回收它,该对象就可以被程序使用.软引用可用来实现内存敏感的高速缓存.软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中. 如果一个对象只具有弱引用,那么在垃圾回收器线程扫描的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存.不过

  • Android 软引用和弱引用详解及实例代码

    Android 软引用 和 弱引用    1.  SoftReference<T>:软引用-->当虚拟机内存不足时,将会回收它指向的对象:需要获取对象时,可以调用get方法. 2.  WeakReference<T>:弱引用-->随时可能会被垃圾回收器回收,不一定要等到虚拟机内存不足时才强制回收.要获取对象时,同样可以调用get方法. 3. WeakReference一般用来防止内存泄漏,要保证内存被虚拟机回收,SoftReference多用作来实现缓存机制(cache

  • Android利用软引用和弱引用避免OOM的方法

    想必很多朋友对OOM(OutOfMemory)这个错误不会陌生,而当遇到这种错误如何有效地解决这个问题呢?今天我们就来说一下如何利用软引用和弱引用来有效地解决程序中出现的OOM问题. 一.了解 强引用.软引用.弱引用.虚引用的概念 在Java中,虽然不需要程序员手动去管理对象的生命周期,但是如果希望某些对象具备一定的生命周期的话(比如内存不足时JVM就会自动回收某些对象从而避免OutOfMemory的错误)就需要用到软引用和弱引用了. 从Java SE2开始,就提供了四种类型的引用:强引用.软引

随机推荐