finalize() trong Java: Vị Cứu Tinh Hay 'Red Flag'?
Java – OOP

finalize() trong Java: Vị Cứu Tinh Hay 'Red Flag'?

Author

Admin System

@root

Ngày xuất bản

21 Mar, 2026

Lượt xem

4 Lượt

finalize() method

Chào các "đệ tử" Gen Z, hôm nay mình cùng "khám phá" một "bí ẩn" trong Java mà nhiều khi các bạn "nghe danh" nhưng chưa chắc đã "thấu đáo" – đó là finalize() method.

Tưởng tượng thế này: Bạn tổ chức một bữa tiệc "siêu to khổng lồ" (aka tạo object trong Java). Khi tiệc tàn, "dọn dẹp" là điều tất yếu, đúng không? Trong Java, cái ông "dọn dẹp" chính là Garbage Collector (GC).

Thế nhưng, đôi khi có những thứ "lỉnh kỉnh" không phải rác thông thường (như file đang mở, kết nối database, tài nguyên native system) mà GC "chưa chắc đã biết đường mà dọn". Lúc này, finalize() được sinh ra với một "sứ mệnh" cao cả: trở thành "người dọn dẹp cuối cùng" trước khi object chính thức bị GC "hốt đi".

Nó là một method được override từ lớp Object, có chữ ký protected void finalize() throws Throwable.

2. Mục đích "thần thánh" và hiện thực "phũ phàng"

  • Mục đích: Hồi xưa, các "cụ" Java nghĩ rằng finalize() sẽ là "cứu cánh" để giải phóng các tài nguyên non-Java (như file handles, socket connections) mà GC không quản lý được trực tiếp. Nó sẽ được gọi một lần duy nhất bởi GC ngay trước khi object bị "xóa sổ" khỏi bộ nhớ.
  • Hiện thực "phũ phàng": Nghe "ngon" đúng không? Nhưng thực tế, finalize() lại là một "red flag" to đùng, một "cơn ác mộng" của lập trình viên. Nó như một lời hứa hẹn "trăng hoa" vậy, không bao giờ đáng tin cậy.

3. Code Ví Dụ: Khi finalize() "lên sóng"

Để các bạn dễ hình dung, mình có một ví dụ "minh họa" về cách finalize() hoạt động. Dù vậy, nhớ kỹ: đừng dùng nó trong code sản phẩm nhé!

class MyResource {
    private String resourceName;
    private boolean isOpen;

    public MyResource(String name) {
        this.resourceName = name;
        this.isOpen = true;
        System.out.println("Tài nguyên " + resourceName + " đã được tạo và mở.");
    }

    public void doSomething() {
        if (isOpen) {
            System.out.println("Đang xử lý với tài nguyên " + resourceName);
        } else {
            System.out.println("Tài nguyên " + resourceName + " đã đóng, không thể xử lý.");
        }
    }

    public void close() {
        if (isOpen) {
            System.out.println("Tài nguyên " + resourceName + " đang được đóng một cách chủ động.");
            this.isOpen = false;
        }
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            if (isOpen) {
                System.out.println("finalize() được gọi: Tài nguyên " + resourceName + " chưa được đóng, đang tự động đóng.");
                // Giả lập việc giải phóng tài nguyên native
                this.isOpen = false;
            } else {
                System.out.println("finalize() được gọi: Tài nguyên " + resourceName + " đã đóng rồi.");
            }
        } finally {
            super.finalize(); // Luôn gọi super.finalize()
        }
    }
}

public class FinalizeDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("--- Bắt đầu Demo finalize() ---");

        // Case 1: Object được tạo và không bao giờ được tham chiếu nữa
        createAndForgetResource("Resource A");

        // Case 2: Object được tạo và đóng chủ động
        MyResource resB = new MyResource("Resource B");
        resB.doSomething();
        resB.close();
        resB = null; // Gỡ bỏ tham chiếu để GC có thể dọn dẹp

        // Case 3: Object được tạo nhưng quên đóng
        createAndForgetResource("Resource C"); // Tài nguyên này sẽ bị quên đóng

        // Gợi ý GC chạy (không đảm bảo GC sẽ chạy ngay)
        System.gc();
        Thread.sleep(100); // Đợi một chút để GC có thể chạy và finalize() được gọi

        System.out.println("--- Kết thúc Demo finalize() ---");
    }

    private static void createAndForgetResource(String name) {
        new MyResource(name); // Tạo object nhưng không gán vào biến -> dễ bị GC dọn
        System.out.println("Object " + name + " đã được tạo và quên đi.");
    }
}
  • Giải thích code:
    • Chúng ta có lớp MyResource mô phỏng một tài nguyên cần được đóng.
    • Method close() là cách "chủ động" để đóng tài nguyên.
    • Method finalize() được override. Nó sẽ kiểm tra nếu tài nguyên chưa đóng thì sẽ "tự động" đóng.
    • Trong main(), mình tạo các trường hợp:
      • Tạo object rồi "quên" nó đi (Case 1, 3) -> hy vọng finalize() sẽ được gọi.
      • Tạo object và đóng nó "đàng hoàng" (Case 2) -> finalize() vẫn có thể được gọi nhưng sẽ thấy tài nguyên đã đóng.
    • System.gc() chỉ là một "gợi ý" cho JVM rằng "ê, mày dọn rác đi!" chứ không đảm bảo nó sẽ chạy ngay lập tức, hay thậm chí là chạy luôn. Đó chính là một trong những điểm "drama" của finalize().
Illustration

4. Mẹo hay và những "drama" của finalize() (Best Practices)

  • Bất khả đoán (Unpredictable): Đây là "drama" lớn nhất. Bạn không bao giờ biết chính xác khi nào finalize() sẽ được gọi, hay thậm chí có được gọi hay không. GC chạy theo thuật toán riêng của nó, và không có gì đảm bảo timing cả.
  • Hiệu năng "như rùa bò" (Performance Overhead): Các object có finalize() sẽ bị đặt vào một "hàng đợi đặc biệt" để GC xử lý. Quá trình này tốn thêm thời gian và tài nguyên, làm chậm quá trình dọn dẹp tổng thể.
  • Rủi ro bảo mật (Security Risks): finalize() có thể bị lợi dụng để "hồi sinh" object sau khi nó đã bị GC "đánh dấu" là rác, gây ra lỗ hổng bảo mật.
  • Exception "ngoài ý muốn": Nếu có exception trong finalize(), nó sẽ bị JVM "nuốt chửng" và không được báo cáo, gây khó khăn cho việc debug.
  • Luôn gọi super.finalize(): Nếu bạn buộc phải override finalize(), hãy nhớ gọi super.finalize() ở khối finally để đảm bảo logic của lớp cha cũng được thực thi.
  • Đã bị "ghẻ lạnh" (Deprecated): Từ Java 9, finalize() đã chính thức bị đánh dấu là @Deprecated và sẽ bị loại bỏ trong các phiên bản tương lai. Điều này có nghĩa là các "cụ" Java cũng đã nhận ra nó "fail" như thế nào.

5. Ứng dụng thực tế (và tại sao lại không dùng)

  • Ngày xưa (xa lắc xa lơ): Hồi Java còn "non trẻ", finalize() đôi khi được dùng trong các thư viện xử lý tài nguyên native (như C/C++ thông qua JNI) để đảm bảo các tài nguyên này được giải phóng nếu lập trình viên quên.
  • Ngày nay (vibe hiện đại): Hầu như không có ứng dụng/website hiện đại nào dùng finalize() nữa. Các nhà phát triển "có kinh nghiệm" đều tránh xa nó như tránh "drama" vậy.
  • Tại sao không dùng? Vì những "drama" ở mục 4 đó các bạn. Nó không đáng tin, chậm chạp và tiềm ẩn rủi ro.

6. Thử nghiệm và Nên dùng cho case nào?

  • Thử nghiệm: Như ví dụ code ở trên, bạn có thể chạy để thấy output. Đôi khi bạn sẽ thấy finalize() được gọi, đôi khi không, hoặc gọi không đúng thứ tự bạn mong muốn. Điều đó chứng minh tính "bất khả đoán" của nó.
  • Nên dùng cho case nào?
    • TRONG THỰC TẾ: HẦU NHƯ KHÔNG BAO GIỜ. Nghe "phũ" nhưng đó là sự thật.
    • Lý thuyết (rất hiếm): Nếu bạn đang làm việc với một hệ thống "cổ đại" mà bắt buộc phải tích hợp với một thư viện native không có cơ chế đóng tài nguyên rõ ràng và không có lựa chọn nào khác ngoài việc dựa vào GC để dọn dẹp, thì may ra. Nhưng ngay cả trong trường hợp đó, bạn cũng phải rất cẩn thận và hiểu rõ rủi ro.
  • Giải pháp thay thế "xịn sò" (Best Practices):
    • try-with-resourcesAutoCloseable: Đây mới là "người hùng" thực sự! Luôn luôn dùng try-with-resources cho các tài nguyên cần đóng (file, database connection, network stream...).
      • Tất cả các class triển khai interface AutoCloseable đều có thể dùng trong try-with-resources.
      • Nó đảm bảo tài nguyên được đóng ngay lập tức khi thoát khỏi khối try, bất kể có lỗi xảy ra hay không.
      • Ví dụ:
        try (FileOutputStream fos = new FileOutputStream("data.txt")) {
            fos.write("Hello Creyt's Gen Z!".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
        // FileOutputStream sẽ tự động đóng khi ra khỏi khối try
        
    • Cleaner API (từ Java 9): Đây là một API mới được giới thiệu để thay thế finalize() một cách an toàn và đáng tin cậy hơn. Nó cho phép bạn đăng ký các hành động dọn dẹp cho các đối tượng khi chúng trở thành "unreachable" (không còn được tham chiếu), nhưng vẫn tách biệt khỏi logic của GC. Tuy nhiên, nó phức tạp hơn và thường chỉ dùng trong các thư viện cấp thấp.

7. Lời kết từ Giảng viên Creyt

Vậy đó, các bạn "đệ tử". finalize() là một khái niệm "hay ho" về mặt lý thuyết nhưng lại là một "thảm họa" trong thực tế.

Hãy nhớ câu thần chú: "Đừng bao giờ tin tưởng vào finalize() để giải phóng tài nguyên quan trọng."

Hãy "flex" kiến thức của mình bằng cách luôn dùng try-with-resourcesAutoCloseable để quản lý tài nguyên một cách "chủ động" và "có trách nhiệm" nhé!

"Keep calm and code on!"

Thuộc Series: Java – OOP

Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!

#tech #cyberpunk #laravel
Chỉnh sửa bài viết

Bình luận (0)

Vui lòng Đăng Nhập để Bình luận

Hỗ trợ Markdown cơ bản
Nguyễn Văn A
1 ngày trước

Tính năng này đỉnh quá ad ơi, chờ mãi mới thấy một blog Tiếng Việt có UI/UX xịn như vầy!