Cơ chế quản lý bộ nhớ thông qua các loại Reference trong Java

Trong Java, việc hiểu rõ cách Garbage Collector (GC) tương tác với các loại tham chiếu khác nhau là yếu tố then chốt để tối ưu hóa bộ nhớ và tránh lỗi OutOfMemoryError. Java cung cấp bốn loại tham chiếu chính: Strong Reference, Soft Reference, Weak Reference và Phantom Reference.

1. Strong Reference (Tham chiếu mạnh)

Đây là loại tham chiếu mặc định và phổ biến nhất trong lập trình Java. Một đối tượng có tham chiếu mạnh sẽ không bao giờ bị GC thu hồi chừng nào tham chiếu đó vẫn còn tồn tại.

// Khởi tạo một tham chiếu mạnh
UserAccount account = new UserAccount(); 

Nếu bộ nhớ đầy, JVM thà ném ra ngoại lệ OutOfMemoryError còn hơn là thu hồi các đối tượng đang có tham chiếu mạnh. Để giúp GC có thể giải phóng đối tượng, ta cần gán biến về null hoặc để nó vượt ra ngoài phạm vi (scope) của phương thức.

Hãy xem xét ví dụ về cách ArrayList quản lý bộ nhớ trong phương thức clear() để tránh rò rỉ bộ nhớ:

public void clear() {
    // Duyệt qua mảng nội bộ và gán null cho từng phần tử
    for (int i = 0; i < currentSize; i++) {
        internalData[i] = null; 
    }
    currentSize = 0;
}

2. Soft Reference (Tham chiếu mềm)

Đối tượng được bao bọc bởi SoftReference sẽ chỉ bị GC thu hồi khi hệ thống thực sự thiếu hụt bộ nhớ. Nếu bộ nhớ vẫn còn dư dả, GC sẽ bỏ qua các đối tượng này.

String temporaryData = new String("Large Content");
SoftReference<String> softRef = new SoftReference<>(temporaryData);
temporaryData = null; // Huỷ tham chiếu mạnh

Ứng dụng: Thường được dùng để xây dựng các bộ nhớ đệm (cache) nhạy cảm với bộ nhớ. Ví dụ: Cache hình ảnh trong ứng dụng Android hoặc nội dung trang web đã tải.

// Giả lập logic truy xuất cache
Resource res = cacheRef.get();
if (res == null) {
    // GC đã thu hồi do thiếu bộ nhớ, cần tải lại
    res = loadResource();
    cacheRef = new SoftReference<>(res);
}

3. Weak Reference (Tham chiếu yếu)

WeakReference có vòng đời ngắn hơn SoftReference. Khi GC chạy, nó sẽ thu hồi ngay lập tức các đối tượng chỉ có tham chiếu yếu trỏ tới, bất kể bộ nhớ hệ thống còn trống hay không.

WeakReference<HeavyObject> weakRef = new WeakReference<>(new HeavyObject());
// Sau khi gọi System.gc(), weakRef.get() thường sẽ trả về null

Dưới đây là ví dụ minh họa việc sử dụng ReferenceQueue để theo dõi các đối tượng bị thu hồi:

ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
WeakReference<byte[]> weakRef = new WeakReference<>(new byte[1024], queue);

System.gc(); // Kích hoạt GC

Reference<? extends byte[]> polledRef = queue.poll();
if (polledRef != null) {
    System.out.println("Đối tượng đã bị GC thu hồi và đưa vào ReferenceQueue");
}

4. Phantom Reference (Tham chiếu ảo)

Đây là loại tham chiếu yếu nhất. Phương thức get() của PhantomReference luôn trả về null. Mục đích duy nhất của nó là thông báo cho chương trình biết rằng đối tượng đã thực sự được giải phóng khỏi bộ nhớ, thường được dùng để thay thế phương thức finalize() nhằm thực hiện các tác vụ dọn dẹp tài nguyên mức thấp.

So sánh các loại Reference

Loại tham chiếu Thời điểm bị thu hồi Mục đích chính
Strong Không bao giờ (trừ khi mất tham chiếu) Sử dụng đối tượng thông thường
Soft Khi bộ nhớ sắp cạn kiệt Làm bộ nhớ đệm (Cache)
Weak Ngay khi GC quét qua Tránh Memory Leak (như trong WeakHashMap)
Phantom Sau khi đối tượng bị giải phóng Dọn dẹp tài nguyên hệ thống

Thử nghiệm thực tế với WeakHashMap

Khi sử dụng một HashMap thông thường để lưu trữ lượng dữ liệu khổng lồ, chương trình rất dễ bị treo do OutOfMemoryError vì các Key được giữ chặt bởi tham chiếu mạnh.

// Sử dụng HashMap thông thường có thể gây OOM
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 100_000_000; i++) {
    map.put(i, "Data chunk " + i);
}

Ngược lại, WeakHashMap tự động loại bỏ các Entry khi Key không còn được tham chiếu ở bất kỳ đâu khác, giúp duy trì bộ nhớ ổn định:

// Sử dụng WeakHashMap để an toàn bộ nhớ hơn
Map<Integer, String> weakMap = new WeakHashMap<>();
for (int i = 0; i < 100_000_000; i++) {
    weakMap.put(i, "Data chunk " + i);
    if (i % 10000 == 0) {
        System.out.println("Current size: " + weakMap.size());
    }
}

Thẻ: Java JVM garbage collection memory management

Đăng vào ngày 26 tháng 7 lúc 10:36