Các dev Java hiếm khi để ý rằng, Java có 4 kiểu tham chiếu chính:
- Strong reference
- Weak reference
- Soft reference
- Phantom reference
Và hôm nay, tôi sẽ giúp các bạn tìm hiểu cả 4 kiểu tham chiếu trên và ứng dụng của chúng thông qua một ví dụ đơn giản nhất.
Giả sử chúng ta có 1 ứng dụng, và một mô-đun trong đó sẽ cần lấy dữ liệu ở một bảng trong Database, bảng này tên là MASTER_TABLE. Chắc chắn các bạn sẽ không để ứng dụng liên tục gọi đến database vì như vậy sẽ làm giảm hiệu năng của chính ứng dụng đó.
Thông thường, mọi người sẽ dùng cache.
Cache là một class, đầu tiên ứng dụng sẽ check phần cache xem dữ liệu có nằm ở đây không, nếu không, nó sẽ check database và đặt phần entry của dữ liệu vào cache. Từ đó, ứng dụng không cần gọi trực tiếp cơ sở dữ liệu mà chỉ cần truy cập vào cache là có thể lấy được dữ liệu cần thiết.
Liệu việc này có thực sự cải thiện hiệu năng?
Câu trả lời là: tùy trường hợp...
Nếu MASTER_TABLE có lượng entry khiêm tốn thì phương pháp này rất phù hợp. Nếu ngược lại, khi MASTER_TABLE có kích thước quá lớn thì phần map tạo bởi Cache sẽ ngày càng lớn theo. Và thay vì cải thiện hiệu năng, phương pháp này có thể hút cạn bộ nhớ hệ thống.
Bạn hãy tưởng tượng đến cảnh tất cả bản ghi trong MASTER_TABLE được load vào cache, khi đó kích thước của cache nó kinh khủng cỡ nào.... Lúc ấy, cache sẽ tiêu hết bộ nhớ của JVM.
Vậy chúng ta cần làm gì....
Giảm số lượng các entry hiện hữu trong cache theo 2 hướng: giới hạn số lượng entry mới và xóa bớt các entry cũ. Tuy vậy, cách này chỉ giải quyết được một nửa của vấn đề, nửa còn lại chính là việc một phần bộ nhớ trong JVM có thể bị chiếm dụng để chứa các object không được sử dụng trong thời gian dài.
Liệu có tồn tại giải pháp lý tưởng?
Có.
Nếu chúng ta có thể tạo cache "động". Tức là cache này có thể tự động thay đổi kích thước theo nhu cầu. Để làm được điều này, chúng ta cần một số kỹ thuật đặc biệt để xóa các entry nằm trong cache quá lâu nhưng không được dùng đến.
Trong Java, để hoàn thành được nhiệm vụ này, chúng ta cần đến các kiểu tham chiếu trong gói java.lang.ref
Trước khi đi vào ví dụ thực tế, hãy dành thời gian tìm hiểu sơ bộ về 4 kiểu tham chiếu này:
Strong reference - tạm dịch: tham chiếu MẠNH.
Hầu hết mọi đoạn code Java đều có sự hiện diện của loại tham chiếu này. Với Strong reference, chúng ta có thể tạo object và gán object đó cho một tham chiếu. Chừng nào một object có tham chiếu mạnh,. nó sẽ không bị thu hồi bởi bộ GC - Garbage Collector.
Một ví dụ về Strong reference:
HelloWorld hello = new HelloWorld();
Soft Reference - tạm dịch: tham chiếu MỎNG.
Nếu một Object không có Strong reference nhưng có Soft Reference, nó có thể bị thu hồi bởi GC khi cần thiết (khi thiếu bộ nhớ). Để lấy một object đang có Soft Reference, ta có thể invoke method get(). Sau khi invoke method get(), nếu object đã bị thu hồi, get() sẽ trả về null.
Weak reference - tạm dịch : tham chiếu YẾU.
Một object không có Strong reference nhưng lại có Weak reference thì trong lần chạy tiếp theo của GC, object này sẽ bị thu hồi cho dù bộ nhớ không bị thiếu.
Phantom reference - tạm dịch: tham chiếu MA.
Đây là một kiểu tham chiếu đặc biệt, nó ám chỉ rằng object này đã "hoàn thành nhiệm vụ" và có thể được GC thu hồi. Nếu một object không có bất kỳ loại tham chiếu nào trong 3 kiểu Strong - Soft - Weak thì rất có thể nó có Phantom reference.
Quay trở lại với ví dụ đầu bài, bây giờ, chúng ta có thể init phần cache theo kiểu WeakHashMap, khi key của WeakHashMap không chứa strong reference, phần bộ nhớ đó sẽ bị GC thu hồi. Như vậy, cache của chúng ta là cache "động", có khả năng tự tăng - giảm kích thước phù hợp với nhu cầu thực tế.
Sau đây là một đoạn code ví dụ cho 4 kiểu tham chiếu trên:
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
public class ReferenceExample {
private String status ="Hi I am active";
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
@Override
public String toString() {
return "ReferenceExample [status=" + status + "]";
}
public void strongReference()
{
ReferenceExample ex = new ReferenceExample();
System.out.println(ex);
}
public void softReference()
{
SoftReference<ReferenceExample> ex = new SoftReference<ReferenceExample>(getRefrence());
System.out.println("Soft refrence :: " + ex.get());
}
public void weakReference()
{
int counter=0;
WeakReference<ReferenceExample> ex = new WeakReference<ReferenceExample>(getRefrence());
while(ex.get()!=null)
{
counter++;
System.gc();
System.out.println("Weak reference deleted after:: " + counter + ex.get());
}
}
public void phantomReference() throws InterruptedException
{
final ReferenceQueue queue = new ReferenceQueue();
PhantomReference<ReferenceExample> ex = new PhantomReference<ReferenceExample>(getRefrence(),queue);
System.gc();
queue.remove();
System.out.println("Phantom reference deleted after");
}
private ReferenceExample getRefrence()
{
return new ReferenceExample();
}
public static void main(String[] args) {
ReferenceExample ex = new ReferenceExample();
ex.strongReference();
ex.softReference();
ex.weakReference();
try {
ex.phantomReference();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Chúng ta thu được đầu ra như sau:
Output :
ReferenceExample [status=Hi I am active]
Soft refrence :: ReferenceExample [status=Hi I am active]
Weak reference deleted after:: 1null
Phantom reference deleted after
Như vậy, khi chúng ta tạo ra Soft Reference, nếu không gian bộ nhớ còn thoải mái thì object có Soft Reference chưa bị GC thu hồi.
Soft refrence :: ReferenceExample [status=Hi I am active]
Với Weak và Phantom Reference, chúng đều bị GC thu hồi.
Weak reference deleted after:: 1null
Phantom reference deleted after
Tham khảo bài viết gốc tại Dzone - tác giả Shamik Mitra
Bình luận