為什麼 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因為 TestOperation(AsyncTask) 是 TestActivity 的 inner class,所以 AsyncTask 會持有一個 reference 指到 TestActivity, 若 AsyncTask 在處理較長時間的工作時,此時離開 TestActivity 或是旋轉螢幕而重新 create,但是因為 AsyncTask 仍保有 Activity reference ,所以原先的 Activity context 無法被回收而形成了 Memory leak。{ @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) {} } }
如何避免 Activity 和 Inner AsyncTask 使用時容易產生 memory leak 方法有不少,在這裡就不多談。
Weak Reference
常見到的使用方法如下:
Widget widget = new Widget(); WeakReferenceweakWidget = 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如上例所示,可以透過 WeakReference 提供的 get() method 來取得曾經記錄的 Object ,若回傳的 object reference 為 null,則代表該 Object 已經被回收。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 } }
若程式中有部份工作需要在背景進行長時間的等待或運算,而某些 Object 可能在其他情況下被回收時,可以多加利用 WeakReference ,如 JNI;因為 WeakReference 的特性可以避免 Memory Leak 產生,也可以透過判斷式來避免使用 Null Object。
Reference: https://weblogs.java.net/blog/enicholas/archive/2006/05/understanding_w.html
沒有留言 :
張貼留言