Tại sao phương thức hoàn thiện lại có trong Java?


44

Theo bài đăng này , chúng ta không bao giờ nên dựa vào phương pháp hoàn thiện để được gọi. Vậy tại sao Java lại đưa nó vào ngôn ngữ lập trình?

Có vẻ như một quyết định khủng khiếp bao gồm trong bất kỳ ngôn ngữ lập trình nào một chức năng có thể được gọi.

Câu trả lời:


41

Theo Java hiệu quả của Joshua Bloch (Ấn bản thứ hai), có hai kịch bản khi finalize()hữu ích:

  1. Một là hoạt động như một mạng lưới an toàn trên mạng trong trường hợp chủ sở hữu của một đối tượng quên gọi phương thức chấm dứt rõ ràng của nó. Mặc dù không có gì đảm bảo rằng bộ hoàn thiện sẽ được gọi ngay lập tức, nhưng có thể tốt hơn là giải phóng tài nguyên muộn hơn là không bao giờ, trong những trường hợp (hy vọng là hiếm) khi khách hàng không gọi được phương thức chấm dứt rõ ràng. Nhưng trình hoàn thiện sẽ ghi lại cảnh báo nếu nhận thấy rằng tài nguyên chưa bị chấm dứt

  2. Việc sử dụng hợp pháp thứ hai của người hoàn thiện liên quan đến các đối tượng với các đồng nghiệp bản địa. Một đồng đẳng bản địa là một đối tượng gốc mà một đối tượng bình thường ủy quyền thông qua các phương thức riêng. Bởi vì một đồng đẳng riêng không phải là một đối tượng bình thường, trình thu gom rác không biết về nó và không thể lấy lại được khi đồng đẳng Java của nó được lấy lại. Bộ hoàn thiện là một phương tiện thích hợp để thực hiện nhiệm vụ này, giả sử rằng đồng đẳng bản địa không có tài nguyên quan trọng. Nếu đồng đẳng bản địa giữ các tài nguyên phải được chấm dứt kịp thời, lớp nên có một phương thức chấm dứt rõ ràng, như được mô tả ở trên. Phương thức chấm dứt nên làm bất cứ điều gì được yêu cầu để giải phóng tài nguyên quan trọng.

Để đọc thêm, hãy tham khảo Mục 7, trang 27.


4
Với âm thanh số 2, âm thanh riêng là nguồn tài nguyên riêng cần có mạng lưới an toàn và cần có phương thức chấm dứt, do đó không nên là một điểm riêng biệt.
Vịt Mooing

2
@MooingDuck: # 2 là một điểm riêng biệt bởi vì, nếu đồng đẳng riêng không chứa các tài nguyên quan trọng (ví dụ: đó hoàn toàn là một đối tượng trong bộ nhớ), thì không cần phải phơi bày một phương thức chấm dứt rõ ràng. Mạng lưới an toàn từ # 1 rõ ràng là về việc có một phương thức chấm dứt.
jhominal

55

Quyết toán là quan trọng cho việc quản lý tài nguyên bản địa. Ví dụ: đối tượng của bạn có thể cần phân bổ WidgetHandle từ hệ điều hành bằng API không phải Java. Nếu bạn không phát hành WidgetHandle đó khi đối tượng của bạn là GC'd, bạn sẽ bị rò rỉ WidgetHandles.

Điều quan trọng là các trường hợp "quyết toán không bao giờ được gọi" bị phá vỡ khá đơn giản:

  1. Chương trình tắt nhanh chóng
  2. Đối tượng "sống mãi mãi" trong suốt thời gian của chương trình
  3. Máy tính tắt / quá trình của bạn bị hệ điều hành / v.v.

Trong cả ba trường hợp này, bạn sẽ không bị rò rỉ riêng (do thực tế là chương trình của bạn không chạy nữa) hoặc bạn đã bị rò rỉ không phải là bản địa (nếu bạn cứ phân bổ các đối tượng được quản lý mà không có chúng GC'd).

Cảnh báo "không dựa vào bộ hoàn thiện được gọi" thực sự là về việc không sử dụng bộ hoàn thiện cho logic chương trình. Ví dụ: bạn không muốn theo dõi xem có bao nhiêu đối tượng của bạn tồn tại trên tất cả các phiên bản của chương trình bằng cách tăng bộ đếm trong một tệp ở đâu đó trong khi xây dựng và giảm nó trong bộ hoàn thiện - bởi vì không có gì đảm bảo rằng các đối tượng của bạn sẽ được hoàn thiện, bộ đếm tệp này có thể sẽ không quay trở về 0. Đây thực sự là một trường hợp đặc biệt của nguyên tắc chung hơn mà bạn không nên phụ thuộc vào chương trình của mình chấm dứt bình thường (mất điện, v.v.).

Tuy nhiên, để quản lý tài nguyên bản địa, các trường hợp trình hoàn thiện không chạy tương ứng với các trường hợp bạn không quan tâm nếu nó không chạy.


Câu trả lời tuyệt vời. Tôi giống như ngay cả khi nó có thể được gọi .. nó vẫn nên được đưa vào. Tôi đã bối rối một lúc với câu hỏi và câu hỏi được liên kết trên StackOverflow.
Được thông báo vào

3
Đó không phải là câu hỏi, nhưng đáng để thảo luận trong bối cảnh "không dựa vào trình hoàn thiện được gọi": Trình hoàn thiện có thể được gọi, nhưng chỉ sau một thời gian trì hoãn tùy ý sau khi đối tượng không thể truy cập được. Điều này quan trọng đối với "tài nguyên bản địa" vốn khan hiếm hơn nhiều so với bộ nhớ (phần lớn trong số chúng, hóa ra).

26
"Quyết toán là quan trọng cho việc quản lý tài nguyên bản địa." - Nooooooooo, ahem xin lỗi. Sử dụng các công cụ hoàn thiện để quản lý tài nguyên bản địa là một ý tưởng khủng khiếp (đó là lý do tại sao chúng tôi có thể tự động hóa và hợp tác ngay bây giờ). GC chỉ quan tâm đến bộ nhớ, không phải bất kỳ tài nguyên nào khác. Điều này có nghĩa là bạn có thể hết tài nguyên bản địa nhưng vẫn có đủ bộ nhớ để không kích hoạt GC - chúng tôi thực sự không muốn điều đó. Ngoài ra các công cụ hoàn thiện làm cho GC đắt hơn cũng không phải là một ý tưởng tốt.
Voo

12
Tôi đồng ý bạn vẫn cần phải có một chiến lược quản lý tài nguyên có nguồn gốc rõ ràng không phải là finalizers, nhưng nếu đối tượng của bạn không phân bổ cho họ và không giải phóng chúng trong finalizer, bạn đang mở lòng ra đón một sự rò rỉ có nguồn gốc trong trường hợp đối tượng là GC'd mà không giải phóng tài nguyên rõ ràng. Nói cách khác, quyết toán là một phần quan trọng của chiến lược quản lý tài nguyên bản địa, không phải là giải pháp cho vấn đề nói chung.
Ryan Cavanaugh

1
@Voo có lẽ đúng hơn khi nói rằng trình hoàn thiện là dự phòng trong trường hợp hợp đồng của đối tượng không được tuân theo ( close()không bao giờ được gọi trước khi nó không thể truy cập được)
ratchet freak

5

Mục đích của phương pháp này được giải thích trong tài liệu API như sau:

nó được gọi nếu và khi máy ảo Java xác định rằng không còn bất kỳ phương tiện nào mà đối tượng này có thể được truy cập bởi bất kỳ luồng nào chưa chết, ngoại trừ kết quả của một hành động được thực hiện bởi việc hoàn thiện một số đối tượng khác hoặc lớp đã sẵn sàng để được hoàn thành ...

mục đích thông thường của finalize... là thực hiện các hành động dọn dẹp trước khi đối tượng bị loại bỏ . Ví dụ: phương thức hoàn thiện cho một đối tượng thể hiện kết nối đầu vào / đầu ra có thể thực hiện các giao dịch I / O rõ ràng để phá vỡ kết nối trước khi đối tượng bị loại bỏ vĩnh viễn ...


Nếu bạn cũng quan tâm đến lý do tại sao các nhà thiết kế ngôn ngữ đã chọn "đối tượng bị loại bỏ" ( rác được thu thập ) theo cách vượt quá sự kiểm soát của lập trình viên ứng dụng ("chúng ta không bao giờ nên dựa vào"), điều này đã được giải thích trong câu trả lời liên quan câu hỏi :

thu gom rác tự động ... loại bỏ toàn bộ các lớp lỗi lập trình mà các lập trình viên C và C ++ gặp phải. Bạn có thể phát triển mã Java với sự tự tin rằng hệ thống sẽ nhanh chóng tìm thấy nhiều lỗi và các vấn đề lớn sẽ không hoạt động cho đến khi mã sản xuất của bạn được chuyển đi ..

Lần lượt, trên trích dẫn, được lấy từ tài liệu chính thức về các mục tiêu thiết kế Java , đó có thể được coi là tài liệu tham khảo có thẩm quyền giải thích lý do tại sao các nhà thiết kế ngôn ngữ Java quyết định theo cách này.

Để thảo luận chi tiết hơn về ngôn ngữ và bất khả tri về sở thích này, hãy tham khảo phần OOSC 9.6 Quản lý bộ nhớ tự động (thực ra, không chỉ phần này mà toàn bộ chương 9 rất đáng đọc nếu bạn quan tâm đến những thứ như vậy). Phần này mở ra với một tuyên bố rõ ràng:

Một môi trường OO tốt sẽ cung cấp một cơ chế quản lý bộ nhớ tự động, nó sẽ phát hiện và lấy lại các đối tượng không thể truy cập, cho phép các nhà phát triển ứng dụng tập trung vào công việc của họ - phát triển ứng dụng.

Các cuộc thảo luận trước sẽ đủ cho thấy tầm quan trọng của việc có sẵn một cơ sở như vậy. Theo lời của Michael Schweitzer và Lambert Strether:

Một chương trình hướng đối tượng không có quản lý bộ nhớ tự động gần giống như một nồi áp suất không có van an toàn: sớm hay muộn điều đó chắc chắn sẽ nổ tung!


4

Các công cụ hoàn thiện tồn tại bởi vì chúng được dự kiến ​​là một phương tiện hiệu quả để đảm bảo mọi thứ được dọn sạch (mặc dù trong thực tế chúng không có) và bởi vì khi chúng được phát minh, các phương tiện tốt hơn để đảm bảo dọn dẹp (như tham chiếu ảo và thử -Các nguồn) chưa tồn tại. Nhìn nhận lại, Java có thể sẽ tốt hơn nếu nỗ lực dành cho việc thực hiện cơ sở "hoàn thiện" của nó đã được dành cho các phương tiện dọn dẹp khác, nhưng điều đó hầu như không rõ ràng trong thời gian Java được phát triển ban đầu.


3

CAVEAT: Tôi có thể đã hết hạn, nhưng đây là cách hiểu của tôi từ vài năm trước:

Nói chung, không có gì đảm bảo khi nào trình hoàn thiện chạy - hoặc thậm chí là nó chạy hoàn toàn, mặc dù một số JVM sẽ cho phép bạn yêu cầu một bản hoàn chỉnh và hoàn tất trước khi chương trình thoát ra (tất nhiên, điều đó có nghĩa là chương trình mất nhiều thời gian hơn để thoát và không phải là chế độ hoạt động mặc định).

Và một số GC được biết là trì hoãn rõ ràng hoặc tránh các đối tượng của GC có bộ hoàn thiện, với hy vọng rằng điều này sẽ tạo ra hiệu suất tốt hơn trên các điểm chuẩn.

Những hành vi này không may xung đột với lý do ban đầu là quyết định cuối cùng được khuyến nghị và đã khuyến khích sử dụng các phương pháp tắt máy rõ ràng được gọi là thay thế.

Nếu bạn có một đối tượng thực sự phải được làm sạch trước khi bị loại bỏ và nếu bạn thực sự không thể tin tưởng người dùng sẽ làm như vậy, một bộ hoàn thiện vẫn có thể đáng xem xét. Nhưng nói chung, có những Lý do Tốt mà bạn không thấy chúng thường xuyên trong mã Java hiện đại như bạn đã làm trong một số ví dụ ban đầu.


1

Các Google Java Style Guide có một số lời khuyên hiền triết về đề tài này:

Nó là cực kỳ hiếm để ghi đè Object.finalize.

Mẹo : Đừng làm điều đó. Nếu bạn hoàn toàn phải, trước tiên hãy đọc và hiểu Mục 7 hiệu quả của Java , "Tránh các công cụ hoàn thiện", sau đó đừng làm điều đó.


5
Tại sao tôi ngưỡng mộ sự đơn giản hóa của vấn đề, "đừng làm điều đó" không phải là một câu trả lời cho "tại sao nó tồn tại".
Pierre Arlaud

1
Tôi đoán tôi đã không đánh vần nó tốt trong câu trả lời của mình, nhưng (IMO) câu trả lời cho "tại sao nó tồn tại?" là "không nên". Đối với những trường hợp hiếm hoi mà bạn thực sự cần một hoạt động quyết toán, có lẽ bạn muốn tự mình xây dựng nó PhantomReferenceReferenceQueuethay vào đó.
Daniel Pryden

Tôi có nghĩa là trong khi * rõ ràng, mặc dù tôi đã không đánh giá thấp bạn. Tuy nhiên, câu trả lời được đánh giá cao nhất cho thấy phương pháp này hữu ích như thế nào, làm mất hiệu lực quan điểm của bạn.
Pierre Arlaud

1
Ngay cả trong trường hợp ngang hàng bản địa, tôi nghĩ PhantomReferencelà một giải pháp tốt hơn. Các công cụ hoàn thiện là một mụn cóc còn sót lại từ những ngày đầu của Java, và các loại giống Object.clone()và thô, là một phần của ngôn ngữ bị lãng quên tốt nhất.
Daniel Pryden

1
Sự nguy hiểm của người hoàn thiện được mô tả theo cách dễ tiếp cận hơn (có thể hiểu được bởi các nhà phát triển) trong C #. Tuy nhiên, người ta không được ngụ ý rằng các cơ chế cơ bản trên Java và C # là giống nhau; không có gì trong thông số kỹ thuật của họ nói như vậy.
rwong

1

Các ngôn ngữ Java Specification (Java SE 7) khẳng định:

Trình hoàn thiện cung cấp cơ hội giải phóng các tài nguyên không thể được giải phóng tự động bởi người quản lý lưu trữ tự động. Trong các tình huống như vậy, chỉ cần lấy lại bộ nhớ được sử dụng bởi một đối tượng sẽ không đảm bảo rằng các tài nguyên mà nó nắm giữ sẽ được thu hồi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.