搜尋此網誌

2013-11-01

Strong Reference and Weak Reference

在 Java 中關於Reference,一般說來分為四種:Strong、Soft、Weak 和 Phantom 。

為什麼 Java 需要四種不同的 Reference 呢?

因為 Java 不像 C/C++ 需要設計師自行管控和釋放記憶體,而是透過 Garbage collector 檢查記憶體 (Heap) 中的 Object reference count / reachability 來決定是否要回收該記憶體以供他處使用, 而這垃圾回收機制(Garbage Collection) 需要知道那些記憶體該被回收、其回收的優先順序為何,所以 Java 提供了四種不同的 reference type 以便進行演算來得知。

Garbage collector 回收記憶體 (Heap) 時,將依據不同的 reference type 的強弱來決定其順序,以下是一個由強至弱的排序:

Strong Reference > Soft Reference > Weak Reference > Phantom Reference

簡單的說,愈弱的 Reference ,其相對應的記憶體(Heap) 愈容易被回收。

Strong Reference

Strong Reference 是我們最常用到的,就像一般的物件宣告和創建。參考下列範例:
StringBuffer buffer = new StringBuffer();
...
...
buffer = null;
System.gc();
當 buffer = null 後,表示新增的 StringBuffer object 此時 reference count 為 0,成為一個可被回收的 Object 。但是 Strong reference 在某些情況下容易成為 memory leak 的來源,看看下列範例:
StringBuffer buffer = new StringBuffer();
...
StringBuffer newBuffer = buffer;
...
buffer = null;
System.gc();
雖然 buffer 已被設為 null,但是仍有另一個 reference (newBuffer) 仍然指到 StringBuffer object,所以該物件並不會被回收。

在 Android 程式中,常見到的 Memory Leak 是發生在 AsyncTask 和 Activity 的交互使用上,如另一篇文章「AsyncTask 的黑暗面」所提到。再來看一個範例:
public class TestActivity extends Activity implements OnClickListener {

    Button btn;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        btn = (Button) findViewById(R.id.button1);
        btn.setOnClickListener(this);
    }

    public void onClick(View view) {
        switch (view.getId()) {
        case R.id.button1:
            new TestOperation().execute("");
            break;
        }
    }

    private class TestOperation extends AsyncTask {

        @Override
        protected String doInBackground(String... params) {
            for (int i = 0; i < 15; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return "Executed";
        }

        @Override
        protected void onPostExecute(String result) {
            ...
            ...
        }

        @Override
        protected void onPreExecute() {}

        @Override
        protected void onProgressUpdate(Void... values) {}
    }
}
因為 TestOperation(AsyncTask) 是 TestActivity 的 inner class,所以 AsyncTask 會持有一個 reference 指到 TestActivity, 若 AsyncTask 在處理較長時間的工作時,此時離開 TestActivity 或是旋轉螢幕而重新 create,但是因為 AsyncTask 仍保有 Activity reference ,所以原先的 Activity context 無法被回收而形成了 Memory leak。

如何避免 Activity  和 Inner AsyncTask 使用時容易產生 memory leak 方法有不少,在這裡就不多談。


Weak Reference


常見到的使用方法如下:
Widget widget = new Widget();
WeakReference weakWidget = new WeakReference(widget);
// do some stuff
....
final Widget test = weakWidget.get();
if(test!=null) {
    ...
    ...
}

有時候我們希望 AsyncTask 在執行過程中,可以用到某個 Activity 本身的一些 method/variables,所以會在 AsyncTask 中持有 Activity reference。如下列:
public class MyTask extends AsyncTask<..., ..., ...> {
    private WeakReference mParentActivity = null;

    public MyTask(MyActivity parentActivity) {
        mParentActivity = new WeakReference(parentActivity);
    }

    @Override
    public ... doInBackground(... params) {
        // do some stuff

        // now we do something that requires the context
        if (mParentActivity.get() != null) {
            // the WeakReference is still valid and hasn't been reclaimed
            // by the GC
            final MyActivity parentActivity = mParentActivity.get();
            if(parentActivity!=null)
                parentActivity.doSomething();
        }

        // return result
    }
}
如上例所示,可以透過 WeakReference 提供的 get() method 來取得曾經記錄的 Object ,若回傳的 object reference 為 null,則代表該 Object 已經被回收。

若程式中有部份工作需要在背景進行長時間的等待或運算,而某些 Object 可能在其他情況下被回收時,可以多加利用 WeakReference ,如 JNI;因為 WeakReference 的特性可以避免 Memory Leak 產生,也可以透過判斷式來避免使用 Null Object。

Reference: https://weblogs.java.net/blog/enicholas/archive/2006/05/understanding_w.html


沒有留言 :

張貼留言