Ngày 06 tháng 12 năm 2023 - Công nghệ thông tin
Trong Java, phương thức finalize()
là một phương thức có sẵn từ lớp Object
. Vì mọi lớp đều kế thừa từ Object
, nên tất cả các lớp đều là con của Object
. Khi chúng ta ghi đè (override) phương thức finalize()
trong lớp con, điều này có nghĩa là chúng ta đang sử dụng Finalizer. Mục đích chính của việc sử dụng Finalizer thường là để thực hiện các thao tác giải phóng tài nguyên cuối cùng trước khi đối tượng bị hủy.
Tuy nhiên, như đã thảo luận trong bài viết “Java try-with-resources Đặc điểm chi tiết”, với các tài nguyên cần được giải phóng, chúng ta có thể đạt được điều này bằng cách triển khai giao diện AutoCloseable
và sử dụng tính năng try-with-resources
. Finalizer chỉ nên được sử dụng trong trường hợp đặc biệt liên quan đến tài nguyên gốc (native resources), tức là những tài nguyên không thuộc sự quản lý của JVM và thường được giải phóng thông qua các phương thức gốc (native methods). Ngoài ra, việc sử dụng Finalizer nên được tránh càng nhiều càng tốt.
Bài viết này nhằm mục đích giải thích lý do tại sao nên tránh sử dụng Finalizer thông qua việc trình bày cơ chế hoạt động của nó cũng như liệt kê các vấn đề về chức năng và hiệu suất mà nó gây ra.
1 Cơ chế hoạt động của Finalizer
Dưới đây là một cái nhìn tổng quan về cách bộ thu gom rác xử lý các đối tượng Finalizer: Khi bộ thu gom rác phát hiện rằng một đối tượng không còn khả năng truy cập (không được bất kỳ luồng nào hoặc đối tượng nào khác tham chiếu), nếu đó là một đối tượng bình thường (không ghi đè phương thức finalize()
), nó sẽ được thu hồi ngay lập tức mà không cần xử lý thêm. Tuy nhiên, nếu đó là một đối tượng Finalizer (đã ghi đè phương thức finalize()
), quy trình xử lý sẽ phức tạp hơn đáng kể. Đối tượng này sẽ được đưa vào hàng đợi Finalizer bởi một luồng JVM (lúc này, các đối tượng khác mà đối tượng này có thể truy cập, dù đã không còn khả năng truy cập, vẫn phải được giữ lại tạm thời). Sau đó, vào một thời điểm không xác định sau này, luồng JVM sẽ lấy đối tượng khỏi hàng đợi và gọi phương thức finalize()
. Chỉ sau khi phương thức này hoàn thành, đối tượng mới thật sự có thể bị thu hồi bởi bộ thu gom rác.
Hình dưới đây minh họa toàn bộ vòng đời của một đối tượng Finalizer từ khi được tạo ra cho đến khi bị phá hủy: !Vòng đời của đối tượng Finalizer (Vòng đời của đối tượng Finalizer)
Sau khi hiểu rõ cách bộ thu gom rác xử lý các đối tượng Finalizer, bây giờ hãy xem xét tại sao Java lại khuyên không nên sử dụng Finalizer?
2 Các vấn đề của Finalizer
Dưới đây là một số vấn đề chính của Finalizer:
-
Thời điểm thực thi không thể đảm bảo Một số đối tượng có thể bị giữ suốt vòng đời của chương trình, dẫn đến chúng không bao giờ được thu hồi, và do đó phương thức
finalize()
cũng sẽ không bao giờ được thực thi. Khi một đối tượng trở nên không khả năng truy cập, thời điểm mà nó sẽ được bộ thu gom rác thu hồi là không chắc chắn, thậm chí có khả năng nó sẽ không bao giờ được thu hồi, dẫn đến lỗiOutOfMemoryError
. -
Các ngoại lệ không được bắt giữ sẽ bị bỏ qua Thông thường, nếu xuất hiện một ngoại lệ không được bắt giữ trong chương trình, luồng sẽ dừng lại và in ra thông tin ngoại lệ. Tuy nhiên, nếu một ngoại lệ không được bắt giữ xảy ra trong khi luồng JVM đang thực thi phương thức
finalize()
của đối tượng Finalizer, ngoại lệ này sẽ bị bỏ qua, quá trình thực thifinalize()
sẽ bị dừng lại mà không in ra bất kỳ thông tin nào. Điều này có thể dẫn đến trạng thái không mong muốn của đối tượng và gây ra hành vi bất thường cho các luồng khác sử dụng đối tượng này. -
Việc triển khai trong
finalize()
có thể gây ra vấn đề giữ lại bộ nhớ Phương thứcfinalize()
là một phương thức bình thường, vì vậy bất kỳ đoạn mã nào cũng có thể được viết bên trong nó, điều này có thể dẫn đến các vấn đề. Như đã mô tả ở phần Nạp Tiền Nohu71 cơ chế hoạt động của Finalizer, khi bộ thu gom rác phát hiện rằng một đối tượng Finalizer không còn khả năng truy cập, đối tượng này sẽ được đưa vào hàng đợi Finalizer. Các đối tượng khác mà đối tượng này có thể truy cập, dù đã không còn khả năng truy cập, vẫn phải được giữ lại tạm thời. Sau đó, vào một thời điểm không xác định, khi luồng JVM gọi phương thứcfinalize()
, đối tượng Finalizer có thể tái tạo tham chiếu đến các đối tượng khác vốn đã không j8bet com còn khả năng truy cập. Điều này sẽ làm cho các đối tượng này không được giải phóng đúng cách. Vì phương thứcfinalize()
chỉ được thực thi một lần duy nhất bởi luồng JVM, khi các đối tượng này trở nên không khả năng truy cập một lần nữa, phương thứcfinalize()
sẽ không được thực thi lại, dẫn đến các đối tượng này không có cơ hội được giải phóng, từ đó gây ra vấn đề giữ lại bộ nhớ.
Như vậy, 99win club bài viết này đã giới thiệu cơ chế hoạt động của Finalizer và liệt kê các vấn đề mà nó gây ra. Kết luận là ngoài việc dọn dẹp tài nguyên gốc, không nên sử dụng Finalizer trong bất kỳ tình huống nào khác.
Cần lưu ý thêm rằng kể từ Java 9, phương thức finalize()
của lớp Object
đã bị đánh dấu là deprecated. Thay vào đó, Java đã giới thiệu Cleaner, mặc dù Cleaner mạnh hơn Finalizer một chút vì chủ sở hữu lớp có quyền kiểm soát phần nào đối với luồng dọn dẹp, nhưng việc thực thi Cleaner vẫn nằm dưới sự kiểm soát của bộ thu gom rác. Do đó, các vấn đề như không thể đảm bảo thời điểm thực thi của Finalizer cũng tồn tại trong Cleaner, vì vậy việc sử dụng Cleaner cũng không được khuyến khích.
[1] Tạo và Hủy Đối tượng: Tránh sử dụng finalizers và cleaners | Effective Java (Ấn bản thứ 3), bởi Joshua Bloch [2] Làm thế nào để Xử lý Vấn đề Giữ Lại Bộ Nhớ Của Finalization trong Java | Oracle - www.oracle.com [3] Tại Sao Không Nên Sử Dụng Finalizers trong Java | Medium - medium.com [4] JEP 421: Đánh Dấu Finalization Là Sẽ Bị Loại Bỏ | OpenJDK - openjdk.org [5] Tại Sao Finalizers Có “Chi Phí Hiệu Suất Nặng Nề”? | Stackoverflow - stackoverflow.com [6] Phương thức finalize() Được Gọi Khi Nào trong Java? | Stackoverflow - stackoverflow.com [7] Java SE 9: Đánh Dấu Phương Thức finalize Là Sẽ Bị Loại Bỏ | Tài Liệu Java Oracle - docs.oracle.com  #Java