内存不够用导致OOM?软引用和弱引用来助你一臂之力

1,879 阅读6分钟

Android历经10余年的迭代,在内存方面取得了很大的进步,但是如何进行内存优化,让应用占用更小的内存,一直都是开发者需要思考的事情。本篇文章主要介绍利用软引用和弱引用,达到内存优化的目的,降低OOM的发生概率。

软引用和弱引用的特点

  • 软引用:新建的对象为软引用,当内存不够时,回收器就会回收这些对象,如果回收后还是没有足够的内存,抛出OutOfMemoryError异常;
  • 弱引用:新建对象为弱引用时,垃圾回收器不管当前内存是否足够,都会回收它的内存;

软引用的使用

简单使用

代码如下:

MyObject object  =  new   MyObject();//创建一个对象
SoftReference<Object> softRef = new SoftReference<Object>( object );//创建对象的软引用
if(softRef != null){
    MyObject anotherRef =(MyObject) aSoftRef .get();//通过软引用获取对象
}

SoftReference 它的一个实例保存着一个 object 对象的软引用,在 object 对象回收之前, SoftReference 类所提供的 get() 方法都会返回 这个object 对象的强引用,一旦回收该 Java 对象之后, get() 方法将返回 null 。所以在获取软引用对象的代码中,一定要判断是否为null,以免出现NullPointerException异常导致应用崩溃。

结合ReferenceQueue使用

代码如下

MyObject object  =  new   MyObject();//创建一个对象
ReferenceQueue queue = new ReferenceQueue();//创建引用队列
SoftReference<Object>  softRef = new SoftReference<Object>( object, queue );// 把引用加入到引用队列

SoftReference 也是一个Java对象,具有Java对象的一般特性,当它保存的对象的软引用被回收时,它也就失去了存在的意义,这时就需要一个适当的清除机制,避免大量 SoftReference 对象带来的内存泄漏,ReferenceQueue就可以解决这个问题。当这个 SoftReference 所软引用的 object 被垃圾收集器回收的同时,softRef 所强引用的 SoftReference 对象被列入 ReferenceQueue 。调用ReferenceQueue的 poll() 方法,如果这个队列中不是空队列,那么将返回队列前面的那个 Reference 对象。 利用这个方法,可以检查哪个 SoftReference 所软引用的对象已经被回收。可以把这些失去 软引用的对象的 SoftReference 对象清除掉,代码如下:

SoftReference ref = null ;
while ((ref = (EmployeeRef) q .poll()) != null ) {
  // 清除 ref
   }

如何利用软引用达到内存合理利用

软引用的生命周期可以是很长的,只要内存足够,就不会被回收, 可以用来实现内存敏感的高速缓存,比如说处理图片这种占用内存大的类。假设在我们的需求中,需要很多的默认图片,比如头像,游戏图标这些,读取图片需要从硬盘读取,这样影响性能,全部加载到内存中,则会有可能导致内存吃紧而发生OOM,在这个时候使用软引用就能很好的解决问题。

private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
<br>....
public void addBitmapToCache(String path) {
// 强引用的Bitmap对象
Bitmap bitmap = BitmapFactory.decodeFile(path);
// 软引用的Bitmap对象
SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);
// 添加该对象到Map中使其缓存
imageCache.put(path, softBitmap);
}
public Bitmap getBitmapByPath(String path) {
// 从缓存中取软引用的Bitmap对象
SoftReference<Bitmap> softBitmap = imageCache.get(path);
// 判断是否存在软引用
if (softBitmap == null) {
return null;
}
// 取出Bitmap对象,如果由于内存不足Bitmap被回收,将取得空
Bitmap bitmap = softBitmap.get();
return bitmap;
}

内存不足时,JVM自动回收缓存图片对象所占用的空间,避免了OOM的问题。

弱引用的使用

简单使用

WeakReference<User> wef = new WeakReference<User>(new User());
if(wef.get() != null){
    User anotherRef =(MyObject) wef .get();//通过软引用获取对象
}

假如在wef.get();执行之前,执行了GC,那么获得的值为null,弱引用是不管内存是否充足,都是会被回收的。

结合ReferenceQueue使用

ReferenceQueue queue = new ReferenceQueue()
WeakReference<User> wef = new WeakReference<User>(new User(),queue);

弱引用和软引用的使用方法类似,就不重复说了。弱引用也可以用来保存那些可有可无的数据,还被经常用来解决有可能产生的内存泄漏问题。

如何利用弱引用避免有可能产生的内存泄漏问题

利用弱引用避免产生内存泄漏的例子有很多,最长见的有Handler的内存泄漏问题。 代码如下

public class MainActivity extends AppCompatActivity {

    private Handler handler  ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        handler = new MyHandler( this ) ;

        new Thread(new Runnable() {
            @Override
            public void run() {
               handler.sendEmptyMessage( 0 ) ;
            }
        }).start() ;

    }

    private static class MyHandler extends Handler {
        WeakReference<MainActivity> weakReference ;

        public MyHandler(MainActivity activity ){
            weakReference  = new WeakReference<MainActivity>( activity) ;
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if ( weakReference.get() != null ){
                // update android ui
            }
        }
    }

}

内部实例会持有外部类引用,所以如果当Activity已经销毁,但是还有消息没处理完毕,那么MyHandler还持有actiivty的引用,这样就会造成内存泄漏。声明一个静态的Handler内部类,并持有外部类的弱引用 ,这样就能很好的防止内存泄漏。

弱引用还会用在内存缓存中,比如我们经常用到的Glide加载图片框架,源码里对图片的处理也用到了软引用技术,感兴趣的可以去探究Glide的缓存策略(没有能力讲明白,在这里就不敢吭声了),源码关键代码如下:

public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
            DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
            Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
        Util.assertMainThread();
        long startTime = LogTime.getLogTime();

        final String id = fetcher.getId();
        EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
                loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
                transcoder, loadProvider.getSourceEncoder());

        //使用LruCache获取缓存
        EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
        if (cached != null) {
            //从缓存中获取资源成功
            cb.onResourceReady(cached);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Loaded resource from cache", startTime, key);
            }
            return null;
        }

        //从弱引用中获取缓存
        EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
        if (active != null) {
            //从缓存中获取资源成功
            cb.onResourceReady(active);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Loaded resource from active resources", startTime, key);
            }
            return null;
        }

        //开启线程从网络中加载图片......
        EngineJob current = jobs.get(key);
        if (current != null) {
            current.addCallback(cb);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Added to existing load", startTime, key);
            }
            return new LoadStatus(cb, current);
        }
    ......
    }

无论是弱引用还是软引用,都可以用来存储可有可无的数据,声明一些生命周期比较长的对象,至于用哪一个,需要视情况而定。

总结

个人认为,想避免OutOfMemory异常的发生,可以考虑使用软引用。从性能上面去考虑,如果对一些占用内存比较大的对象使用弱引用,就能更及时的被回收。软引用被回收的概率要比弱引用更低,经常要使用的对象,使用软引用更好。对应用进行内存优化是很重要的,软引用和弱引用技术都能让使用内存更合理化,但是要视情况而定,不能滥用。