我们应该了解的那些事-Reference

来源:互联网 发布:js 动态设置margin 编辑:程序博客网 时间:2024/05/23 01:18

  • 引用的概念
    • 关于垃圾回收
      • 对象可达性
      • 垃圾回收机制
    • 引用类型和引用队列
  • 我们如何合理的使用它们
  • 结语

引用的概念

Java中为我们引入了引用这个类,它可以帮助我们引用对象并且可以更高效的利用内存。

Reference<T>1 我们今天的主角,先看一下官方给它说明:

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.

引用的结构

它三个直接子类分别是:PhantomReference<T>,SoftReference<T>, WeakReference<T>,同时它们与GC回收有着密切的关联。接下来我们了解一下Java中的垃圾回收机制和如果标记一个对象是可回收的,然后系统性的介绍一下这几个引用类型,最后我们结合着代码介绍一下这几个类是如何使用的。

关于垃圾回收

对象可达性

对象的可达性,什么类型的可达性关乎着这个对象什么时候会被回收。

  • 强可达对象(strongly reachable):
    如果一个对象通过强引用可达或者通过强引用链可达的话这种对象就成为强可及对象,这种情况下的对象垃圾回收器不予理睬。如果我们开发过程不需要垃圾回器回收该对象,就直接将该对象赋为前引用。

  • 软可达对象(weakly reachable):
    可以通过软引用访问的对象就成为软可及对象,当内存不够的时候GC会回收这类内存。

  • 弱可达对象(weakly reachable):
    如果一个对象不是强可及对象,也不是软可及对象,仅通过弱应用访问的我们称之为弱可及对象。

  • 虚可达对象(phantom reachable):
    简单来说以上都不是,仅通过虚引用访问的对象就是虚可及对象了。

  • 不可达到状态(unreachable):
    对象没有被任何类型的引用引用着,因此被回收了。我们可以称之为不可达状态

垃圾回收机制

我们知道Java中的垃圾回收都是有GC回收器自动处理的,它是一个系统级的垃圾回收(GC,Garbage Collection)线程,在GC回收的时候会触发STW(stop the world),就是说在GC回收的时候会挂起除GC线程之外的其他线程,当然native代码还是能够继续进行,频繁的GC回收会导致页面卡顿。

现在的GC回收算法大多数使用了根集(root set)这个概念,垃圾回收首先需要确定从根开始哪些是可达的和哪些是不可达的,从根集可达的对象都是活动对象,它们不能作为垃圾被回收,这也包括从根集间接可达的对象。而根集通过任意路径不可达的对象符合垃圾收集的条件,应该被回收。下面介绍几个常用的算法。

  • 引用计数法(Reference Counting Collector)
    引用计数法是唯一没有使用根集的垃圾回收的算法,该算法使用引用计数器来区分存活对象和不再使用的对象。但引用计数器增加了程序执行的开销,因为每次对象赋给新的变量,计数器加1,而每次现有对象出了作用域生,计数器减1。

  • tracing算法(Tracing Collector)
    为了解决引用计数法所引出的问题,提出了tracing算法,它使用了根集的概念。垃圾收集器从根集开始扫描,识别出哪些对象可达,哪些对象不可达,并用某种方式标记可达对象。从而根据标记清理垃圾对象。

这里写图片描述

还有其他经过优化或解决碎片问题的算法这里就不赘述了,简单罗列一下大家有兴趣可以自行了解一下,包括compacting算法,copying算法,generation算法.

引用类型和引用队列

  • 强引用(StrongReference):强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。这里给出的是他们一句话的介绍,详细的一定要到官网上看一下哦!一定一定!

  • 软引用(SoftReference)2:官方对它的描述

    Soft reference objects, which are cleared at the discretion of the garbage collector in response to memory demand.

    翻译过来可以理解为 软引用只有在内存不足时会被GC回收器回收掉。
    这么一看软引用非常适合来做cache,但事实上Android并不推荐大家使用它来做cache,因为软引用不能够提供足够的信息来告诉GC回收器,它所引用的对象是应该被回收或者应该被保持着。看到这里大家会有疑问,我们该怎么办呢?有没有能够替代软引用来做cache的呢?这里买个关子 最后的时候我们再来介绍~

  • 弱引用(WeakReference)3:官方对它的描述

    Weak reference objects, which do not prevent their referents from being made finalizable, finalized, and then reclaimed. Weak references are most often used to implement canonicalizing mappings.

    这一大段的定义和解释我们可以简单的理解为 弱引用没办法阻止GC回收器回收或终结一个对象,当GC回收器扫描到弱引用时就会将其回收,同时官方告诉我们弱引用通常用来实现规范化映射.
    在知名第三方图片加载库Universal-Image-Loader中就是用了弱引用来维护Bitmap对象,从而减少bitmap对象对于内存的消耗。

  • 虚引用(PhantomReference)4:官方对它的描述

    Phantom reference objects, which are enqueued after the collector determines that their referents may otherwise be reclaimed.

    虚引用所引用的对象我们称之为幽灵对象,虚引用唯一的用处就是对于引用对象的跟踪和收集,它必须与ReferenceQueue一起使用充当一个通知的角色,监控引用的对象何时放入到引用队列中。
    它可以知道对象何时会从内存被删除,这个特性可以让我们将它用于一些特殊需求。因为它所引用的对象在被放入引用队列之后不会立刻被回收,直到这个对象是一个不可达到状态或者是没有引用的时候才会被回收。

  • 引用队列(ReferenceQueue)5:官方对它的描述

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

    引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用添加到该队列中。它可以与其他引用类型配合使用,当引用对象指向的内存空间被回收后,这个引用对象就会被放入引用队列之中。

我们如何合理的使用它们?

  • SoftReference 的替代者 LruCache

    从上面的介绍来看软引用适合用来实现内存敏感的高速缓存,但是由于它所存在的问题Android推出了一个替代它的方案LruCache,看一下官方给出的例子

    int cacheSize = 4 * 1024 * 1024; // 4MiB LruCache<String, Bitmap> bitmapCache = new LruCache<String,     Bitmap>(cacheSize) {     protected int sizeOf(String key, Bitmap value) {        return value.getByteCount();     } }

LruCache引用着有限个数的或者有限大小的强引用对象,通过队列操作控制对象或者数据的更新和回收,LruCache维护着一个LinkedHashMap,由它来保存对象的<K, V>
大家看到HashMap就会想,它是一个线程不安全的的容器类,那在并发操作的时候怎么办?这点不用担心LruCache已经做了相应的处理,在关键的方法上都加了同步锁。
现在大多数的数据缓存或者自己封装的图片加载类都是用了LruCache,可以很好的帮助我们解决OOM的问题。

  • WeakReference

    • 典型例子 Universal-Image-Loader 6
      ImagLoader在对于内存缓存时,没有使用我们上面所说的LruCache,而是自定义了一个
      抽象类BaseMemoryCache和接口MemoryCache,使用SynchronizedMap<String, Reference<Bitmap>>来保存缓存的Bitmap对象,这里它使用Reference<T>为了给子类提供扩展空间,通过抽象方法createReference(Bitmap value)实现扩展。
      ImageLoader中提供非常多的缓存回收算法供开发者使用,例如最大优先删除,最少使用优先删除,最久优先删除等等,但是ImageLoader在实现的时候是有瑕疵的。所有LimitedMemoryCache子类都有个问题,就是 Bitmap 虽然通过WeakReference包装,但实际根本不会被虚拟机回收,因为他们子类中同时都保留了 Bitmap 的强引用。只有在这个强引用被删除了WeakReference才有可能被回收,大都是 UIL 早期实现的版本,不推荐使用。所以大家使用的时候如果需要上述的算法支持,继承BaseMemoryCache重写。或者使用两个没有问题的缓存LruMemoryCacheWeakMemoryCache

      回归正题,虽然有些许的不足但是其设计理念还是很不错的,ImageLoader中所实现的这些回收机制,基本上都实现了createReference(Bitmap value)方法(LruMemoryCache除外)。统一都是使用了WeakReference,源码如下

      @Overrideprotected Reference<Bitmap> createReference(Bitmap value) {    return new WeakReference<Bitmap>(value);}

      通过WeakReference的包装,利用其回收机制从而减少Bitmap对象对内存的压力。

    • 防止内存泄露
      我们都知道最容易出现内存泄漏的方式就是:Activity的内部类引用Activity实例,这样会导致Activity不能被释放。Handler是我们最常用的线程间消息通讯方式,它也是最容易发生内存泄漏的罪魁祸首之一。那我们我就可以通过使用WeakReference来包装Activity、Context、Fragment,从而防止内存泄漏的发生。

      static class MyHandler extends Handler{    WeakReference<XXXActivity> mactivity;    public MyHandler(XXXActivity activity){        mactivity = new WeakReference<XXXActivity>(activity);    }    @Override    public void handleMessage(Message msg) {        super.handleMessage(msg);                       switch (msg.what) {            }    }}

      Asynctask是我们另一个容易发生内存泄漏的,所以我们也可以通过WeakReference来包装。系统中提供了一个我们无法继承的模板WeakAsyncTask,我们可以模仿其实现一个WeakAsyncTask

      public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget> extends          AsyncTask<Params, Progress, Result> {      protected WeakReference<WeakTarget> mTarget;      public WeakAsyncTask(WeakTarget target) {          mTarget = new WeakReference<WeakTarget>(target);      }      @Override      protected final void onPreExecute() {          final WeakTarget target = mTarget.get();          if (target != null) {              this.onPreExecute(target);          }      }      @Override      protected final Result doInBackground(Params... params) {          final WeakTarget target = mTarget.get();          if (target != null) {              return this.doInBackground(target, params);          } else {              return null;          }      }      @Override      protected final void onPostExecute(Result result) {          final WeakTarget target = mTarget.get();          if (target != null) {              this.onPostExecute(target, result);          }      }      protected void onPreExecute(WeakTarget target) {          // No default action      }      protected abstract Result doInBackground(WeakTarget target, Params... params);      protected void onPostExecute(WeakTarget target, Result result) {          // No default action      }  } 

结语

内存优化门道很多,引用只是其中一部分。下篇会介绍一下关于内存优化的部分。
这次关于引用的介绍就到这里了~ 欢迎大家多提意见多交流~


  1. https://developer.android.com/reference/java/lang/ref/Reference.html ↩
  2. https://developer.android.com/reference/java/lang/ref/SoftReference.html ↩
  3. https://developer.android.com/reference/java/lang/ref/WeakReference.html ↩
  4. https://developer.android.com/reference/java/lang/ref/PhantomReference.html ↩
  5. https://developer.android.com/reference/java/lang/ref/ReferenceQueue.html ↩
  6. http://a.codekk.com/detail/Android/huxian99/Android%20Universal%20Image%20Loader%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90 ↩
5 0
原创粉丝点击