Bài 7: Loại bỏ obsolete object references
Bad practice thường gặp (hướng cũ)
import java.util.*;
public class Stack {
private Object[] elements = new Object[16];
private int size = 0;
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0) throw new EmptyStackException();
return elements[--size]; // BAD: vẫn giữ reference trong mảng
}
private void ensureCapacity() {
if (elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
Vì sao cách này không tốt
- Object đã “không còn dùng” về mặt logic nhưng vẫn bị giữ reference vật lý.
- Garbage Collector không thể reclaim memory của object đó.
- Dễ gây memory leak âm thầm, nhất là với container tự quản lý memory.
Cách giải quyết vấn đề
Khi một class tự quản lý bộ nhớ (ví dụ: mảng nội bộ, pool, cache, listener list), bạn phải chủ động xóa những reference đã lỗi thời. Nếu không, object sẽ bị giữ sống ngoài ý muốn.
Trong Java, memory leak thường không đến từ việc quên free như C/C++, mà đến từ việc giữ reference lâu hơn vòng đời nghiệp vụ của object. Nói ngắn gọn: object đã "chết" về mặt logic nhưng vẫn "sống" trong heap vì còn bị trỏ tới.
Thông điệp quan trọng là: nếu bạn tự quản lý vùng lưu trữ object, bạn cũng phải tự quản lý việc xóa reference khi object không còn cần thiết. Đây là trách nhiệm thiết kế, không phải việc để GC "tự lo".
Một điểm thực chiến dễ bỏ sót là các cấu trúc observer/listener và cache. Chúng thường chạy đúng trong giai đoạn đầu, nhưng sau thời gian dài sẽ phình bộ nhớ nếu thiếu cơ chế unregister hoặc eviction.
Vì vậy, hướng giải quyết chuẩn là:
- clear reference ngay khi remove/pop/dequeue.
- định nghĩa rõ vòng đời của entry trong cache và listener.
- ưu tiên cấu trúc thư viện đã có semantics quản lý vòng đời thay vì tự viết tay nếu không bắt buộc.
Ví dụ sửa đúng:
public Object pop() {
if (size == 0) throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // clear obsolete reference
return result;
}
Những nơi hay phát sinh obsolete reference
- Self-managed memory structures: stack, queue, object pool tự viết.
- Caches: phần tử hết hạn nhưng không bị loại bỏ.
- Callbacks/listeners: đăng ký xong không hủy đăng ký.
Cách phòng tránh trong thực tế
- Xóa reference ngay khi phần tử bị loại bỏ.
- Với cache, dùng cơ chế eviction rõ ràng (size/time-based).
- Với listener, cung cấp API unregister và enforce vòng đời.
- Ưu tiên dùng cấu trúc chuẩn thư viện thay vì tự quản lý nếu không cần.
Checklist code review
- Cấu trúc dữ liệu tự viết có clear slot khi remove/pop không?
- Có danh sách listener nào tăng mãi theo thời gian không?
- Cache có chính sách eviction hay chỉ add mà không remove?
- Có chỉ số leak trên môi trường chạy dài hạn (heap growth) không?
Tóm lại: leak trong Java không chỉ do thiếu free như ngôn ngữ thủ công; nó thường đến từ reference sống quá lâu. Hãy chủ động loại bỏ obsolete reference ở đúng thời điểm.
All rights reserved