java 軟引用、弱引用、強引用、虛引用的解析
寫了那麼多篇文章第一次使用MarkDown編輯器。。。
- 軟引用與強引用、弱引用、虛引用的對比
軟引用、強引用、弱引用、虛引用
- 強引用
- 弱引用
- 虛引用
- 軟引用
強引用
強引用也就是我們一般使用的引用,如若一個物件有強引用,那麼即使記憶體不足的情況出現,強引用物件也不會被輕易的回收
String s = new String();
建立了一個String物件,並用一個變數s儲存對這個物件的引用。這就是個強引用。
變數持有的是這個物件的引用。通常,引用是一個物件的儲存地址(但不同於c++、c,這個引用不能轉換成整數)。而強引用其實就是正在使用的物件
虛引用
虛引用其實沒什麼用,和弱引用一樣不會介入引用物件的生命週期。它的get方法總是返回null,所以你得不到它引用的物件。它的唯一作用就是跟蹤物件合適被回收,但是必須要和引用佇列 (ReferenceQueue)聯合使用。當垃圾回收機制準備回收一個物件的時候,如果發現這個物件還存在一個虛引用,就會在這個物件被回收之前將這個虛引用加入到引用佇列中,如果在引用佇列中發現虛引用,那麼就說明與之關聯的物件將要被回收了。
弱引用
如果一個物件持有的是弱引用,垃圾回收機制是會去回收這個物件的,但是垃圾回收是優先順序很低的執行緒,所以不一定很快能被回收。這個會在什麼情況應用呢?看過android官網圖片載入demo的同學們應該就比較能理解,我這裡就直接拿過來用,作為弱引用的講解例子。
class BitmapWorkerTask extends AsyncTask {
private final WeakReference imageViewReference;
private int data = 0;
public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference(imageView);
}
// Decode image in background.
@Override
protected Bitmap doInBackground(Integer... params) {
data = params[0];
return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
}
// Once complete, see if ImageView is still around and set bitmap.
@Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
這裡就有一個imageview的弱引用,AsyncTask 這個大家都應該不陌生就不再介紹,這裡在這個非同步載入執行緒中載入圖片,然後顯示到imageview上面。而imageview是在ui執行緒(也就是主執行緒),我們不能保證使用者什麼時候會結束這個主執行緒,而此時已經啟動了這個圖片非同步載入執行緒,我們不知道它什麼時候會載入完,如果執行緒擁有的是imageview的強引用的話,這個imageview在使用者退出的時候就不能被回收,也就會造成記憶體洩露的情況,在使用handler的時候也經常會遇到這個問題,就是你在activity中啟動一個執行緒,使用handler處理資訊,如果activity退出,而執行緒是不會退出的,還會持有handler物件,也就造成了handler不能被回收,記憶體洩露就出現了。而弱引用就解決了這個問題,像上面的程式碼,ui執行緒結束imageview此時要回收,因為圖片載入執行緒中持有的是imageview的弱引用,他不會影響他所弱引用物件的回收,也就不會造成記憶體洩露,而此時如果弱引用的物件已經被回收,imageViewReference.get();得到的就是空
軟引用
軟引用在記憶體優化、速度優化中是經常會被用到的,軟引用可用來實現記憶體敏感的快取記憶體。如果一個物件有一個軟引用,那麼只要記憶體空間足夠,他就不會被回收,我們就可以使用這些物件,相當於回收利用吧。我們知道開闢記憶體的消耗還是比較大的(不管是事件還是空間上),所以複用就可以做到減少記憶體的消耗,速度優化等。可以和引用佇列聯合使用。
這裡要再次說下android官網的圖片載入demo,還是建議大家去看看。
這裡又要用到這裡面的例子程式來講軟引用,但是他的demo中沒有結合引用佇列使用,下面就結合demo介紹:
首先來說說他的應用場景:非同步載入大量的圖片,大量圖片的話就必定會用到記憶體快取以提高速度。使用的是LruCache來快取圖片,也就是結合了最近最久未使用演算法的快取,這個快取要有大小限制的(要是把全部記憶體都佔了那肯定不行),也就是會有圖片從這個快取中移除,這個時候就用到了軟引用,將那些所有被移出快取的圖片(bitmap物件)繫結軟引用,加入到一個容器中儲存,然後等到需要新的bitmap物件的時候就從這個容器中迴圈拿可以使用的進行復用(建立新的bitmap記憶體很耗時,也很費記憶體,複用大大提高效率)。
下面就是被移除的物件新增軟引用的程式碼
mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
// Notify the removed entry that is no longer being cached.
@Override
protected void entryRemoved(boolean evicted, String key,
BitmapDrawable oldValue, BitmapDrawable newValue) {
if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
// The removed entry is a recycling drawable, so notify it
// that it has been removed from the memory cache.
((RecyclingBitmapDrawable) oldValue).setIsCached(false);
} else {
// The removed entry is a standard BitmapDrawable.
if (Utils.hasHoneycomb()) {
// We're running on Honeycomb or later, so add the bitmap
// to a SoftReference set for possible use with inBitmap later.
mReusableBitmaps.add
(new SoftReference<Bitmap>(oldValue.getBitmap()));
}
}
}
....
迴圈找可以複用的bitmap
protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
Bitmap bitmap = null;
if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
synchronized (mReusableBitmaps) {
final Iterator<SoftReference<Bitmap>> iterator
= mReusableBitmaps.iterator();
Bitmap item;
while (iterator.hasNext()) {
item = iterator.next().get();
if (null != item && item.isMutable()) {
// Check to see it the item can be used for inBitmap.
if (canUseForInBitmap(item, options)) {
bitmap = item;
// Remove from reusable set so it can't be used again.
iterator.remove();
break;
}
} else {
// Remove from the set if the reference has been cleared.
iterator.remove();
}
}
}
}
return bitmap;
}
上面我們說了要結合引用佇列使用,但是上面的而梨子中其實沒有用到。先說說引用佇列的用處吧:
String s = new String();
ReferenceQueue queue = new ReferenceQueue();
SoftReference ref=new SoftReference(s, queue);
如上述程式碼 ,一開始s是強引用,然後建立了一個引用佇列,以及結合了引用佇列的軟引用,此時如果s=null;(上面也講到了,垃圾回收是一個優先順序很低的執行緒,所以不會那麼快回收),但這裡假設s被回收了,那麼ref就會加入到queue中,此時ref通過get方法得到的結果已經是null了,我們可以利用這個來清除沒有用的軟引用,如果像上面這樣是單個的軟引用物件的話就可以不用,但是很多時候我們會有一堆軟引用然後都放在容器中,這個時候就需要去清理其中沒有用的軟引用,因為如果那個存軟引用的容器存了一堆沒用的,也是很大的消耗。
引用佇列的使用以及為什麼要使用也說了,為什麼上面android官網的載入圖片的demo中沒有用到引用佇列大家結合一下程式碼看看就知道了,引用佇列就一個用處:清理沒用的軟引用,而上面的demo中我們可以看到他迴圈讀取其中的軟引用,判斷是否為空(也就是沒用了的軟引用)等條件,不符合就直接移除了,也就做了清理沒用的軟引用的工作。
講完了。。。最後要說,軟引用、弱引用真的是比較好用的,一定要學會靈活使用。