Viblo Learning
+6

How References work in Java and Android

Trong Java, GC (Garbage Collector) là chương trình chạy nền, làm nhiệm vụ theo dõi các đối tượng trong bộ nhớ, và tiến hành thu hồi bộ nhớ (quá trình Garbage Collection) của các đối tượng khi chúng không còn được tham chiếu đến. Developer cần nắm biết khi nào đối tượng được xem là thỏa mãn điều kiện để GC thu hồi, bởi chúng ta không hề mong muốn các đối tượng không cần thiết được lưu giữ trong bộ nhớ. Trong bài viết này, chúng ta sẽ cùng tìm hiểu thêm về Reference (tạm dịch là tham chiếu) trong Java nói chung và Android nói riêng.

Khái niệm

A reference is the direction of an object that is annotated, so you can access it.

tạm dịch là:

Một tham chiếu chỉ đến một đối tượng được khai báo, nhờ đó ta có thể truy cập được nó.

Phân loại

Trong Java, Reference được chia làm 4 loại là:

  • Strong reference (tạm dịch: tham chiếu mạnh)
  • Weak reference (tạm dịch: tham chiếu yếu)
  • Soft reference (tạm dịch: tham chiếu mềm)
  • Phantom reference (tạm dịch: tham chiếu ma)

Chúng ta sẽ lần lượt đi qua từng loại reference này.

Strong reference

Là loại tham chiếu mặc định, và thường gặp trong Java. Khi tạo một đối tượng thì mặc định một Strong reference được tạo ra bên trong đối tượng.

MyObject obj = new MyObject();

Một đối tượng sẽ không bị GC thu hồi nếu như có bất cứ một chuỗi Strong reference nào liên kết với nó.

public class StrongRefDemo {
    static class MyObject {
        @ Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("I'm collected!"); // will be printed if  MyObject is collected by GC
        }
    }
    static MyObject obj;
    public static void main(String[] args) throws InterruptedException {
        System.out.println("Start");
        obj = new MyObject(); // New strong reference is created.
        obj = null; // To remove strong reference from obj
        System.gc(); // Force to call Garbage collector
        Thread.sleep(5000); // Wait GC finish its job
        System.out.println("End");
    }
}

Trong ví dụ trên, đối tượng obj mất đi Strong reference sau khi gán =null nên thỏa mãn điều kiện để GC thu hồi bộ nhớ (dòng I'm collected! được in ra trong log).

Note:

Trong java, hàm finalize() được GC gọi trên đối tượng khi GC nhận thấy rằng không còn một tham chiếu nào tới đối tượng đó.

strongRef.png

Xét ví dụ tiếp sau đây:

public static void main(String args[]) throws InterruptedException {
        System.out.println("Start");
        obj = new MyObject();
        List<MyObject> list = new ArrayList<MyObject>();
        list.add(obj);
        obj = null;
        System.gc();
        Thread.sleep(5000);
        System.out.println("End");
    }

Trong trường hợp này, đối tượng obj tuy gán =null. Nhưng trước đó, nó đã được thêm vào trong list nên sau khi gán null, nó vẫn là 1 Strongly reachable vì tồn tại 1 đối tượng có tham chiếu đến nó. Do vậy, đối tượng obj sẽ không bị thu hồi.

Để GC có thể thu hồi obj ta cần hủy các tham chiếu đến nó bằng cách gán list = null hoặc list.clear().

Lấy 1 ví dụ thường thấy trong android:

public class MainActivity extends Activity {
    @ Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        new MyAsyncTask().execute();
    }

    private class MyAsyncTask extends AsyncTask {
        @ Override
        protected Object doInBackground(Object[] params) {
            return doSomeStuff();
        }
        private Object doSomeStuff() {
            //do something to get result
            return new MyObject();
        }
    }
}

Trong đoạn code này đối tượng MyAsyncTaskStrong reference với MainActivity, theo đó có 1 vấn đề nảy sinh khi MainActivity bị hủy (onDestroy) và cần được thu hồi bộ nhớ, lúc này giả sử MyAsyncTask vẫn đang thực thi và giữ tham chiếu đến MainActivity, thì điều này làm cho GC không thể thu hồi bộ nhớ của activity, dẫn đến tình trạng leak memory.

WeakReference

Weak reference objects, which do not prevent their referents from being made finalizable, finalized, and then reclaimed.

tạm dịch (+ thêm bớt 😄 )

Tham chiếu yếu là tham chiếu mà không đủ mạnh để giữ đối tượng đó trong bộ nhớ khi bị yêu cầu thu hồi (từ GC)

weakRef.png

JVM sẽ bỏ qua với Weak reference, các đối tượng weakly reachable object có thể truy cập qua method get() nếu như nó vẫn còn trong bộ nhớ.

public static void main(String args[]) {
    obj = new MyObject();
    WeakReference<MyObject> weakObj = new WeakReference<>(obj)
    obj = null; // obj is now eligible for garbage collection.
    // System.gc(); // if gc run here, obj is completely collected
    obj = weakObj.get(); // try to retrieve back obj
}

Ở ví dụ này, obj có thể được thu hồi sau khi gán null nếu nó vẫn tồn tại trong bộ nhớ (GC chưa chạy).

Trong android thì sao, xét ví dụ sau đây:

public class MainActivity extends Activity {
    @ Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new MyAsyncTask(this).execute();
    }
    private static class MyAsyncTask extends AsyncTask {
        private WeakReference<MainActivity> mainActivity;

        public MyAsyncTask(MainActivity mainActivity) {
            this.mainActivity = new WeakReference<>(mainActivity);
        }
        @ Override
        protected Object doInBackground(Object[] params) {
            return doSomeStuff();
        }
        private Object doSomeStuff() {
            //do something to get result
            return new Object();
        }
        @ Override
        protected void onPostExecute(Object object) {
            super.onPostExecute(object);
            if (mainActivity.get() != null){
                //adapt contents
            }
        }
    }
}

Khác với trong ví dụ với Strong reference, MainActivity bị leak memory. Trong ví dụ này MyAsyntask tham chiếu với MainActivity thông qua WeakReference.

private WeakReference<MainActivity> mainActivity;
...
this.mainActivity = new WeakReference<>(mainActivity);

Do vậy, khi MainActivity bị hủy, WeakReference giúp nó được GC thu hồi, và không còn tình trạng memory leak.

SoftReference

SoftReference giống với WeakReference ở điểm nó có thể được thu hồi khi GC cần, tuy nhiên điểm khác biệt là trong khi WeakReference được thu hồi ngay lập tức, còn SoftReference lại phụ thuộc vào GC. Tất cả các SoftReference được cho vào các reference Queue và xóa khi cần. GC đảm bảo rằng tất cả các SoftReference tới các đối tượng softly reachable sẽ được thu hồi trước khi hệ thống văng lỗi OutOfMemory, ưu tiên giữ lại những tham chiếu là recently-created hoặc recently-used.

Trong android, không khuyên khích việc sử dụng SoftReference cho việc tạo cache. Lý do là hệ thống sẽ không có đủ thông tin để xóa hoặc giữ tham chiếu nào và có thể dẫn đến tình trạng heap tăng cao. Việc thiếu thông tin này làm giảm công dụng của SoftReference, GC có thể thu hồi sớm những tham chiếu còn dùng, hoặc hao phí bộ nhớ khi giữ những tham chiếu không cần thiết. Vậy nên sử dụng android.util.LruCache thay cho SoftReference khi tạo cache.

PhantomReference

An object is phantom reachable if it is neither strongly nor softly nor weakly reachable and has been finalized and there is a path from the roots to it that contains at least one phantom reference.

tạm dịch (khá khó hiểu)

Một đối tượng là phantom reachable nếu như nó không phải là strongly, weakly hay softly reachable, đã ở trạng thái finalized (tức lúc GC xác định là không còn tham chiếu nào tới đối tượng này) và có 1 đường đi từ roots tới đối tượng đó chứa ít nhất 1 phantom reference

Khác với các tham chiếu khác, PhantomReference không tự động bị thu hồi bởi GC mà nó sẽ được cho vào ReferenceQueue, một đối tượng phantom reachable sẽ tồn tại đến khi tất cả các tham chiếu bị hủy hoặc bản thân nó trở thành unreachable. Khác với WeakReference and SoftReference, hàm get() trong PhantomReference luôn trả về null

(Dịch sang tiếng Việt khá khó hiểu phần này 😃 , mọi người có thể tham khảo chi tiết hơn về PhantomReference tại đây )

Kết luận

Qua bài viết hi vọng mọi người có thêm 1 ít thông tin hữu ích về những loại reference trong java, và biết được khi nào đối tượng sẽ được GC thu hồi bộ nhớ.

Với android, tránh sử dụng non-static inner Class , mà nên sử dụng static inner Class kết hợp WeakReference để tránh memory leak. Tránh sử dụng SoftReference cho việc tạo cache trong android.

Tham khảo:

https://dzone.com/articles/finalization-and-phantom https://developer.android.com/reference/java/lang/ref/PhantomReference.html https://developer.android.com/reference/java/lang/ref/WeakReference.html https://developer.android.com/reference/java/lang/ref/SoftReference.html

https://medium.com/google-developer-experts/finally-understanding-how-references-work-in-android-and-java-26a0d9c92f83#.3jo1x2v9y


All Rights Reserved