How References work in Java and Android
Bài đăng này đã không được cập nhật trong 3 năm
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àmfinalize()
đượcGC
gọi trên đối tượng khiGC
nhận thấy rằng không còn một tham chiếu nào tới đối tượng đó.
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 MyAsyncTask
có Strong 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)
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áifinalized
(tức lúcGC
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 1phantom 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
All rights reserved