Bài 8: Tránh finalizer và cleaner
Bad practice thường gặp (hướng cũ)
public class FileResource {
private final java.io.FileInputStream in;
public FileResource(String path) throws Exception {
this.in = new java.io.FileInputStream(path);
}
@Override
protected void finalize() throws Throwable {
in.close(); // BAD: trông có vẻ an toàn nhưng không đáng tin cậy
}
}
Vì sao cách này không tốt
- Thời điểm chạy finalizer/cleaner là không xác định.
- Không đảm bảo được sẽ chạy kịp lúc, hoặc thậm chí có thể không chạy trước khi process kết thúc.
- Có chi phí hiệu năng cao và làm hành vi giải phóng resource khó đoán.
Cách tác giả giải quyết vấn đề
Đừng dùng finalizer/cleaner như cơ chế chính để đóng resource quan trọng (file handle, socket, DB connection, lock...). Hãy dùng cơ chế đóng tường minh và deterministic.
Diễn giải tương tự theo tinh thần tác giả
Ý cốt lõi là: quản lý tài nguyên phải có thời điểm đóng rõ ràng, dự đoán được, và do lập trình viên chủ động điều khiển. finalizer hoặc cleaner không đáp ứng tiêu chí đó vì thời điểm chạy phụ thuộc GC.
Khi phụ thuộc vào cơ chế không xác định thời gian, hệ thống dễ rơi vào trạng thái "chạy được một lúc rồi tụt dần": file descriptor cạn, connection pool nghẽn, hoặc độ trễ tăng do cleanup đến muộn.
Giải pháp đúng là chuyển từ tư duy "để runtime dọn giúp" sang tư duy "đóng resource như một phần của contract API". Nghĩa là class phải hỗ trợ đóng tường minh, và caller phải dùng theo pattern an toàn như try-with-resources.
Cleaner nếu có thì chỉ là lớp bảo hiểm cuối cùng cho các trường hợp client dùng sai API, không phải luồng chuẩn của hệ thống.
Hướng làm đúng:
- Cho class implement
AutoCloseable. - Người dùng class đóng bằng
try-with-resources. - Nếu thật sự cần, cleaner chỉ nên là mạng an toàn cuối cùng, không phải đường chính.
Ví dụ đúng:
public final class FileResource implements AutoCloseable {
private final java.io.FileInputStream in;
public FileResource(String path) throws Exception {
this.in = new java.io.FileInputStream(path);
}
@Override
public void close() throws Exception {
in.close();
}
}
Khi nào cleaner có thể chấp nhận
- Dùng như safety net để ghi log/ràng buộc cleanup phụ khi client quên close.
- Hoặc xử lý native resource đặc biệt, nơi close tường minh vẫn là con đường chính.
Rủi ro bảo mật/vận hành
- Resource leak kéo dài có thể làm cạn file descriptor.
- Dễ tạo hiệu ứng “chạy được lúc đầu, chết sau vài giờ/ngày”.
- Khó debug vì lỗi xuất hiện muộn và không ổn định theo thời điểm GC.
Checklist code review
- Class giữ resource hệ điều hành có implement
AutoCloseablechưa? - Có chỗ nào còn dựa vào
finalize()hoặc cleaner để đóng resource không? - API có ép caller đóng resource bằng pattern rõ ràng không?
- Test integration có mô phỏng vòng đời dài để phát hiện leak không?
Tóm lại: finalizer/cleaner không phải công cụ quản lý tài nguyên đáng tin cậy cho luồng chính. Hãy đóng resource tường minh, sớm, và dự đoán được.
All rights reserved