Tại sao mô hình hủy diệt đối tượng trong các ngôn ngữ được thu thập rác lại vắng mặt?


27

Tìm kiếm cái nhìn sâu sắc về các quyết định xung quanh rác thu thập thiết kế ngôn ngữ. Có lẽ một chuyên gia ngôn ngữ có thể khai sáng cho tôi? Tôi đến từ một nền tảng C ++, vì vậy khu vực này gây trở ngại cho tôi.

Dường như gần như tất cả các ngôn ngữ được thu thập rác hiện đại có hỗ trợ đối tượng OOPy như Ruby, Javascript / ES6 / ES7, Actioncript, Lua, v.v ... hoàn toàn bỏ qua mô hình hủy diệt / hoàn thiện. Python dường như là người duy nhất có class __del__()phương thức của nó . Tại sao lại thế này? Có các hạn chế về chức năng / lý thuyết trong các ngôn ngữ với bộ sưu tập rác tự động ngăn cản việc triển khai hiệu quả phương pháp hủy / hoàn thiện trên các đối tượng không?

Tôi thấy rất thiếu khi các ngôn ngữ này coi bộ nhớ là tài nguyên duy nhất đáng để quản lý. Điều gì về ổ cắm, xử lý tập tin, trạng thái ứng dụng? Không có khả năng triển khai logic tùy chỉnh để dọn sạch các tài nguyên không có bộ nhớ và trạng thái hoàn thiện đối tượng, tôi bắt buộc phải xả ứng dụng của mình bằng các myObject.destroy()cuộc gọi kiểu tùy chỉnh , đặt logic dọn dẹp bên ngoài "lớp" của mình, phá vỡ đóng gói đã cố gắng và loại bỏ ứng dụng rò rỉ tài nguyên do lỗi của con người thay vì tự động được xử lý bởi gc.

Các quyết định thiết kế ngôn ngữ dẫn đến các ngôn ngữ này không có cách nào để thực thi logic tùy chỉnh khi xử lý đối tượng? Tôi phải tưởng tượng có một lý do tốt. Tôi muốn hiểu rõ hơn về các quyết định kỹ thuật và lý thuyết dẫn đến việc các ngôn ngữ này không hỗ trợ cho việc hủy / hoàn thiện đối tượng.

Cập nhật:

Có lẽ một cách tốt hơn để diễn đạt câu hỏi của tôi:

Tại sao một ngôn ngữ sẽ có khái niệm tích hợp của các thể hiện đối tượng với các cấu trúc giống như lớp hoặc lớp cùng với khởi tạo tùy chỉnh (hàm tạo), nhưng hoàn toàn bỏ qua chức năng hủy / hoàn thiện? Các ngôn ngữ cung cấp bộ sưu tập rác tự động dường như là ứng cử viên chính để hỗ trợ tiêu hủy / hoàn thiện đối tượng như họ biết chắc chắn 100% khi một đối tượng không còn được sử dụng. Tuy nhiên, hầu hết các ngôn ngữ đó không hỗ trợ nó.

Tôi không nghĩ đó là trường hợp mà hàm hủy có thể không bao giờ được gọi, vì đó sẽ là rò rỉ bộ nhớ lõi, mà gcs được thiết kế để tránh. Tôi có thể thấy một đối số có thể là bộ hủy / bộ hoàn thiện có thể không được gọi cho đến khi một thời gian không xác định trong tương lai, nhưng điều đó không ngăn Java hoặc Python hỗ trợ chức năng.

Các lý do thiết kế ngôn ngữ cốt lõi để không hỗ trợ bất kỳ hình thức hoàn thiện đối tượng là gì?


9
Có lẽ bởi vì finalize/ destroylà một lời nói dối? Không có gì đảm bảo nó sẽ được thực thi. Và, ngay cả khi, bạn không biết khi nào (được đưa vào bộ sưu tập rác tự động) và nếu bối cảnh cần thiết vẫn còn đó (nó có thể đã được thu thập). Vì vậy, an toàn hơn để đảm bảo trạng thái nhất quán theo các cách khác và người ta có thể muốn buộc lập trình viên làm như vậy.
Raphael

1
Tôi nghĩ rằng câu hỏi này là ngoại biên giới. Đây có phải là một câu hỏi thiết kế ngôn ngữ lập trình thuộc loại chúng ta muốn giải trí, hay nó là một câu hỏi cho một trang web có định hướng hơn về lập trình? Cộng đồng bỏ phiếu.
Raphael

14
Đó là một câu hỏi hay trong thiết kế PL, hãy có nó.
Andrej Bauer

3
Đây không thực sự là một sự phân biệt tĩnh / động. Nhiều ngôn ngữ tĩnh không có bộ hoàn thiện. Trong thực tế, không phải ngôn ngữ với người hoàn thiện trong thiểu số?
Andrej Bauer

1
nghĩ rằng có một số câu hỏi ở đây ... sẽ tốt hơn nếu bạn xác định các thuật ngữ nhiều hơn một chút. java có một khối cuối cùng không bị ràng buộc với phá hủy đối tượng mà thoát khỏi phương thức. cũng có những cách khác để đối phó với các nguồn lực. ví dụ: trong java, nhóm kết nối có thể xử lý các kết nối không được sử dụng [x] trong thời gian và lấy lại chúng. không thanh lịch nhưng nó hoạt động. một phần của câu trả lời cho câu hỏi của bạn là bộ sưu tập rác gần như không phải là quy trình, không phải là quá trình tức thời và không bị điều khiển bởi các đối tượng không được sử dụng nữa mà do các ràng buộc / trần nhà được kích hoạt.
vzn

Câu trả lời:


10

Mô hình mà bạn đang nói đến, nơi các đối tượng biết cách làm sạch tài nguyên của họ, rơi vào ba loại có liên quan. Chúng ta đừng kết hợp các công cụ hủy diệt với các công cụ hoàn thiện - chỉ có một liên quan đến việc thu gom rác:

  • Mẫu hoàn thiện : phương thức dọn dẹp được khai báo tự động, được xác định bởi lập trình viên, được gọi tự động.

    Bộ hoàn thiện được gọi tự động trước khi xử lý bởi người thu gom rác. Thuật ngữ này áp dụng nếu thuật toán thu gom rác được sử dụng có thể xác định vòng đời đối tượng.

  • Mẫu hủy : phương thức dọn dẹp được khai báo tự động, được xác định bởi lập trình viên, đôi khi chỉ được gọi tự động.

    Các cấu trúc hủy có thể được gọi tự động cho các đối tượng được cấp phát ngăn xếp (vì tuổi thọ của đối tượng là xác định), nhưng phải được gọi một cách rõ ràng trên tất cả các đường dẫn thực thi có thể cho các đối tượng được phân bổ heap (vì tuổi thọ của đối tượng là không xác định).

  • Các mẫu tiêu hủy : Phương pháp làm sạch công bố, xác định, và được gọi là bởi lập trình viên.

    Các lập trình viên thực hiện một phương thức xử lý và tự gọi nó - đây là nơi myObject.destroy()phương thức tùy chỉnh của bạn rơi xuống. Nếu xử lý là hoàn toàn bắt buộc, thì phải xử lý các lệnh xử lý trên tất cả các đường dẫn thực thi có thể.

Cuối cùng là droids bạn đang tìm kiếm.

Mẫu hoàn thiện (mẫu mà câu hỏi của bạn đang hỏi về) là cơ chế liên kết các đối tượng với tài nguyên hệ thống (ổ cắm, mô tả tệp, v.v.) để khai hoang lẫn nhau bởi trình thu gom rác. Nhưng, các công cụ hoàn thiện về cơ bản là do sự xót xa của thuật toán thu gom rác đang sử dụng.

Hãy xem xét giả định này của bạn:

Các ngôn ngữ cung cấp bộ sưu tập rác tự động ... biết chắc chắn 100% khi một đối tượng không còn được sử dụng.

Kỹ thuật sai (cảm ơn bạn, @babou). Thu gom rác về cơ bản là về bộ nhớ, không phải đối tượng. Nếu hoặc khi thuật toán thu thập nhận ra bộ nhớ của một đối tượng không còn được sử dụng thì phụ thuộc vào thuật toán và (có thể) cách các đối tượng của bạn đề cập đến nhau. Hãy nói về hai loại trình thu gom rác thời gian chạy. Có rất nhiều cách để thay đổi và tăng cường những kỹ thuật cơ bản này:

  1. Truy tìm dấu vết. Những dấu vết bộ nhớ, không phải đối tượng. Trừ khi được tăng cường để làm như vậy, họ không duy trì các tham chiếu trở lại các đối tượng từ bộ nhớ. Trừ khi được tăng cường, các GC này sẽ không biết khi nào một đối tượng có thể được hoàn thành, ngay cả khi họ biết khi nào bộ nhớ của nó không thể truy cập được. Do đó, các cuộc gọi quyết toán không được đảm bảo.

  2. Đếm tham chiếu GC . Chúng sử dụng các đối tượng để theo dõi bộ nhớ. Họ mô hình hóa khả năng tiếp cận đối tượng với một biểu đồ tham chiếu có hướng. Nếu có một chu trình trong biểu đồ tham chiếu đối tượng của bạn, thì tất cả các đối tượng trong chu trình sẽ không bao giờ được gọi là trình hoàn thiện của chúng (cho đến khi kết thúc chương trình, rõ ràng). Một lần nữa, các cuộc gọi quyết toán không được đảm bảo.

TLD

Thu gom rác rất khó khăn và đa dạng. Một cuộc gọi quyết toán không thể được đảm bảo trước khi kết thúc chương trình.


Bạn đúng rằng đây không phải là v. Động. Đó là một vấn đề với các ngôn ngữ được thu gom rác. Thu gom rác là một vấn đề phức tạp và có lẽ là lý do chính vì có nhiều trường hợp cần xem xét (ví dụ: điều gì xảy ra nếu logic trong finalize()nguyên nhân khiến đối tượng được dọn sạch sẽ được tham chiếu lại?). Tuy nhiên, việc không thể đảm bảo trình hoàn thiện được gọi trước khi kết thúc chương trình không ngăn Java hỗ trợ nó. Không nói câu trả lời của bạn là không chính xác, có lẽ không đầy đủ. Vẫn là một bài viết rất tốt. Cảm ơn bạn.
dbcb

Cảm ơn vì bạn đã phản hồi. Đây là một nỗ lực để hoàn thành câu trả lời của tôi: Bằng cách bỏ qua các công cụ hoàn thiện một cách rõ ràng, một ngôn ngữ buộc người dùng phải quản lý tài nguyên của chính họ. Đối với nhiều loại vấn đề, đó có lẽ là một bất lợi. Cá nhân, tôi thích sự lựa chọn của Java, bởi vì tôi có sức mạnh của những người hoàn thiện không có gì ngăn cản tôi viết và sử dụng công cụ xử lý của riêng tôi. Java nói, "Này, lập trình viên. Bạn không phải là một thằng ngốc, vì vậy đây là một công cụ hoàn thiện. Hãy cẩn thận."
kdbanman

1
Cập nhật câu hỏi ban đầu của tôi để phản ánh rằng điều này liên quan đến ngôn ngữ được thu gom rác. Chấp nhận câu trả lời của bạn. Cảm ơn đã dành thời gian trả lời.
dbcb 2/2/2015

Vui vẻ giúp đỡ. Nhận xét của tôi làm rõ câu trả lời của tôi rõ ràng hơn?
kdbanman

2
Thật tốt Đối với tôi, câu trả lời thực sự ở đây là các ngôn ngữ chọn không thực hiện nó bởi vì giá trị cảm nhận không vượt quá các vấn đề khi thực hiện chức năng. Điều đó không phải là không thể (như Java và Python chứng minh), nhưng có một sự đánh đổi mà nhiều ngôn ngữ chọn không thực hiện.
dbcb 2/2/2015

5

Tóm lại

Hoàn thiện không phải là vấn đề đơn giản để xử lý bởi người thu gom rác. Nó rất dễ sử dụng với tính toán tham chiếu GC, nhưng họ GC này thường không đầy đủ, đòi hỏi phải rò rỉ bộ nhớ bằng cách kích hoạt rõ ràng sự phá hủy và hoàn thiện một số đối tượng và cấu trúc. Truy tìm bộ thu gom rác hiệu quả hơn nhiều, nhưng chúng khiến việc xác định và hủy bỏ đối tượng khó khăn hơn nhiều, trái ngược với việc chỉ xác định bộ nhớ không sử dụng, do đó đòi hỏi phải quản lý phức tạp hơn, tốn thời gian và không gian, và phức tạp việc thực hiện.

Giới thiệu

Tôi giả định rằng những gì bạn đang hỏi là tại sao các ngôn ngữ được thu gom rác không tự động xử lý việc hủy / hoàn thiện trong quy trình thu gom rác, như được nêu trong nhận xét:

Tôi thấy rất thiếu khi các ngôn ngữ này coi bộ nhớ là tài nguyên duy nhất đáng để quản lý. Điều gì về ổ cắm, xử lý tập tin, trạng thái ứng dụng?

Tôi không đồng ý với câu trả lời được chấp nhận bởi kdbanman . Mặc dù các sự kiện đã nêu hầu hết là chính xác, mặc dù rất thiên về việc đếm tham chiếu, tôi không tin rằng họ giải thích chính xác tình huống phàn nàn trong câu hỏi.

Tôi không tin rằng thuật ngữ được phát triển trong câu trả lời đó là vấn đề lớn, và nó có nhiều khả năng gây nhầm lẫn. Thật vậy, như đã trình bày, thuật ngữ chủ yếu được xác định theo cách các thủ tục được kích hoạt chứ không phải bởi những gì họ làm. Vấn đề là trong mọi trường hợp, cần phải hoàn thiện một đối tượng không còn cần thiết với một số quy trình dọn dẹp và để giải phóng bất kỳ tài nguyên nào nó đã sử dụng, bộ nhớ chỉ là một trong số đó. Lý tưởng nhất, tất cả nên được thực hiện tự động khi đối tượng không còn được sử dụng, bằng phương tiện của bộ thu gom rác. Trong thực tế, GC có thể bị thiếu hoặc có thiếu sót, và điều này được bù đắp bằng cách kích hoạt rõ ràng bằng chương trình hoàn thiện và khai hoang.

Việc trigerring rõ ràng bởi chương trình là một vấn đề vì nó có thể cho phép khó phân tích lỗi lập trình, khi một đối tượng vẫn đang sử dụng đang bị chấm dứt rõ ràng.

Do đó, tốt hơn nhiều là dựa vào thu gom rác tự động để lấy lại tài nguyên. Nhưng có hai vấn đề:

  • một số kỹ thuật thu gom rác sẽ cho phép rò rỉ bộ nhớ ngăn chặn việc cải tạo toàn bộ tài nguyên. Điều này nổi tiếng với việc đếm tham chiếu GC, nhưng có thể xuất hiện cho các kỹ thuật GC khác khi sử dụng một số tổ chức dữ liệu mà không cần quan tâm (điểm không được thảo luận ở đây).

  • trong khi kỹ thuật GC có thể tốt trong việc xác định tài nguyên bộ nhớ không còn được sử dụng, việc hoàn thiện các đối tượng có trong đó có thể không đơn giản và điều đó làm phức tạp vấn đề lấy lại các tài nguyên khác được sử dụng bởi các đối tượng này, thường là mục đích của việc hoàn thiện.

Cuối cùng, một điểm quan trọng thường bị lãng quên là các chu trình GC có thể được kích hoạt bởi bất cứ thứ gì, không chỉ là thiếu bộ nhớ, nếu các móc thích hợp được cung cấp và nếu chi phí của chu trình GC được coi là xứng đáng. Do đó, hoàn toàn ổn khi bắt đầu một GC khi bất kỳ loại tài nguyên nào bị thiếu, với hy vọng giải phóng một số.

Tham khảo đếm người thu gom rác

Đếm tham chiếu là một kỹ thuật thu gom rác yếu , sẽ không xử lý đúng chu kỳ. Nó thực sự sẽ yếu khi phá hủy các cấu trúc lỗi thời và đòi lại các tài nguyên khác đơn giản chỉ vì nó yếu trong việc lấy lại bộ nhớ. Nhưng các công cụ hoàn thiện có thể được sử dụng dễ dàng nhất với bộ thu gom rác tham chiếu (GC), vì một công cụ đếm số ref sẽ lấy lại cấu trúc khi số lượng ref của nó giảm xuống 0, tại thời điểm đó, địa chỉ của nó được biết cùng với loại của nó, hoặc là tĩnh hoặc năng động. Do đó, có thể lấy lại bộ nhớ một cách chính xác sau khi áp dụng bộ hoàn thiện thích hợp và gọi đệ quy quy trình trên tất cả các đối tượng nhọn (có thể thông qua quy trình hoàn thiện).

Tóm lại, việc hoàn thiện rất dễ thực hiện với Ref Counting GC, nhưng bị "không hoàn chỉnh" của GC đó, thực sự là do các cấu trúc hình tròn, chính xác đến mức mà việc phục hồi bộ nhớ phải chịu. Nói cách khác, với số tham chiếu, bộ nhớ được quản lý kém như các tài nguyên khác như ổ cắm, tay cầm tệp, v.v.

Thật vậy, Ref Count GC không có khả năng lấy lại các cấu trúc vòng lặp (nói chung) có thể được xem là rò rỉ bộ nhớ . Bạn không thể mong đợi tất cả các GC để tránh rò rỉ bộ nhớ. Nó phụ thuộc vào thuật toán GC và vào thông tin cấu trúc kiểu có sẵn một cách linh hoạt (ví dụ trong GC bảo thủ ).

Truy tìm người thu gom rác

Họ GC mạnh hơn, không bị rò rỉ như vậy, là họ truy tìm khám phá các phần sống của bộ nhớ, bắt đầu từ các con trỏ gốc được xác định rõ. Tất cả các phần của bộ nhớ không được truy cập trong quá trình theo dõi này (thực sự có thể bị phân tách theo nhiều cách khác nhau, nhưng tôi phải đơn giản hóa) là những phần không được sử dụng của bộ nhớ có thể được lấy lại 1 . Các bộ sưu tập này sẽ lấy lại tất cả các phần bộ nhớ mà chương trình không thể truy cập được nữa, bất kể nó làm gì. Nó đòi lại các cấu trúc vòng tròn, và GC tiên tiến hơn dựa trên một số biến thể của mô hình này, đôi khi rất tinh vi. Nó có thể được kết hợp với đếm tham chiếu trong một số trường hợp và bù đắp cho điểm yếu của nó.

Một vấn đề là tuyên bố của bạn (ở cuối câu hỏi):

Các ngôn ngữ cung cấp bộ sưu tập rác tự động dường như là ứng cử viên chính để hỗ trợ tiêu hủy / hoàn thiện đối tượng như họ biết chắc chắn 100% khi một đối tượng không còn được sử dụng.

là kỹ thuật không chính xác để truy tìm các nhà sưu tập.

Những gì được biết đến với sự chắc chắn 100% là những phần của bộ nhớ không còn được sử dụng . (Chính xác hơn, nên nói rằng chúng không còn truy cập được nữa , bởi vì một số phần, không còn có thể được sử dụng theo logic của chương trình, vẫn được xem xét sử dụng nếu vẫn còn một con trỏ vô dụng đối với chúng trong chương trình dữ liệu.) Nhưng cần xử lý thêm và các cấu trúc phù hợp để biết những đối tượng không sử dụng có thể đã được lưu trữ trong những phần hiện chưa sử dụng này của bộ nhớ . Điều này không thể được xác định từ những gì đã biết của chương trình, vì chương trình không còn được kết nối với các phần của bộ nhớ.

Do đó, sau khi vượt qua bộ sưu tập rác, bạn chỉ còn lại những mảnh bộ nhớ chứa các đối tượng không còn sử dụng, nhưng không có cách nào để biết những đối tượng này là gì để áp dụng hoàn thiện chính xác. Hơn nữa, nếu trình thu thập dấu vết là loại đánh dấu và quét, thì có thể một số đoạn có thể chứa các đối tượng đã được hoàn thành trong một lần vượt qua trước đó, nhưng không được sử dụng vì lý do phân mảnh. Tuy nhiên điều này có thể được xử lý bằng cách sử dụng gõ rõ ràng mở rộng.

Mặc dù một bộ sưu tập đơn giản sẽ lấy lại những mảnh bộ nhớ này, nhưng không cần phải quảng cáo thêm nữa, việc hoàn thiện đòi hỏi một đường chuyền cụ thể để khám phá bộ nhớ không sử dụng đó, xác định các đối tượng có trong đó và áp dụng các quy trình hoàn thiện. Nhưng một cuộc thăm dò như vậy đòi hỏi phải xác định loại đối tượng được lưu trữ ở đó, và xác định loại cũng là cần thiết để áp dụng hoàn thiện thích hợp, nếu có.

Vì vậy, điều đó bao hàm thêm chi phí trong thời gian GC (vượt qua thêm) và có thể chi phí bộ nhớ thêm để cung cấp thông tin loại thích hợp trong thời gian đó bằng các kỹ thuật đa dạng. Những chi phí này có thể là đáng kể vì người ta thường chỉ muốn hoàn thiện một vài đối tượng, trong khi chi phí không gian và thời gian có thể liên quan đến tất cả các đối tượng.

Một điểm khác là chi phí thời gian và không gian có thể liên quan đến việc thực thi mã chương trình, và không chỉ thực hiện GC.

Tôi không thể đưa ra câu trả lời chính xác hơn, chỉ vào các vấn đề cụ thể, vì tôi không biết chi tiết cụ thể của nhiều ngôn ngữ bạn liệt kê. Trong trường hợp của C, đánh máy là một vấn đề rất khó dẫn đến sự phát triển của các nhà sưu tập bảo thủ. Tôi đoán là điều này cũng ảnh hưởng đến C ++, nhưng tôi không phải là chuyên gia về C ++. Điều này dường như được xác nhận bởi Hans Boehm , người đã thực hiện nhiều nghiên cứu về GC bảo thủ. GC bảo thủ không thể lấy lại chính xác tất cả các bộ nhớ không sử dụng vì nó có thể thiếu thông tin loại chính xác trên dữ liệu. Vì lý do tương tự, nó sẽ không thể áp dụng một cách có hệ thống các quy trình hoàn thiện.

Vì vậy, có thể làm những gì bạn đang yêu cầu, như bạn biết từ một số ngôn ngữ. Nhưng nó không miễn phí. Tùy thuộc vào ngôn ngữ và cách triển khai ngôn ngữ, nó có thể đòi hỏi một chi phí ngay cả khi bạn không sử dụng tính năng này. Các kỹ thuật và sự đánh đổi khác nhau có thể được xem xét để giải quyết các vấn đề này, nhưng điều đó nằm ngoài phạm vi của một câu trả lời có kích thước hợp lý.

1 - đây là một bản trình bày trừu tượng về bộ sưu tập theo dõi (bao gồm cả bản sao và dấu quét và quét), mọi thứ khác nhau tùy theo loại bộ sưu tập theo dõi và khám phá phần không sử dụng của bộ nhớ là khác nhau, tùy thuộc vào việc sao chép hoặc đánh dấu và quét được sử dụng.


Bạn cung cấp rất nhiều chi tiết tuyệt vời về bộ sưu tập rác. Tuy nhiên, câu trả lời của bạn không thực sự không đồng ý với tôi - bản tóm tắt và TLDR của bạn về cơ bản là nói cùng một điều. Và với giá trị của nó, câu trả lời của tôi sử dụng tham chiếu đếm GC làm ví dụ, không phải là "thiên vị mạnh".
kdbanman

Sau khi đọc kỹ hơn, tôi thấy sự bất đồng. Tôi sẽ chỉnh sửa cho phù hợp. Ngoài ra, thuật ngữ của tôi là không rõ ràng. Câu hỏi được đưa ra là những người hoàn thiện và phá hủy, và thậm chí đã đề cập đến việc xử lý trong cùng một hơi thở. Giá trị của nó để lan truyền những từ đúng.
kdbanman

@kdbanman Khó khăn là tôi đã giải quyết cả hai bạn, vì câu trả lời của bạn đang đứng như một tài liệu tham khảo. Bạn không thể sử dụng số tham chiếu làm ví dụ mẫu vì nó là một GC yếu, hiếm khi được sử dụng trong các ngôn ngữ (kiểm tra các ngôn ngữ được trích dẫn bởi OP), trong đó việc thêm các công cụ hoàn thiện sẽ thực sự dễ dàng (nhưng với việc sử dụng hạn chế). Truy tìm dấu vết hầu như luôn luôn được sử dụng. Nhưng những người hoàn thiện rất khó để bám vào chúng, bởi vì những vật thể chết không được biết đến (trái với tuyên bố mà bạn cho là đúng). Phân biệt giữa gõ tĩnh và gõ động là không liên quan, bằng cách gõ động lưu trữ dữ liệu là điều cần thiết.
babou

@kdbanman Về thuật ngữ, nói chung nó rất hữu ích, vì nó tương ứng với các tình huống khác nhau. Nhưng ở đây không có ích gì, vì câu hỏi là về việc chuyển quyết toán cho GC. Các GC cơ bản được cho là chỉ thực hiện việc phá hủy. Điều cần thiết là một thuật ngữ phân biệt getting memory recycled, mà tôi gọi reclamationvà thực hiện một số dọn dẹp trước đó, chẳng hạn như lấy lại các tài nguyên khác hoặc cập nhật một số bảng đối tượng mà tôi gọi finalization. Chúng dường như là những vấn đề liên quan, nhưng tôi có thể đã bỏ lỡ một điểm trong thuật ngữ của bạn, điều này mới đối với tôi.
babou

1
Cảm ơn @kdbanman, babou. Thảo luận tốt. Tôi nghĩ rằng cả hai bài viết của bạn bao gồm các điểm tương tự. Như cả hai bạn chỉ ra, vấn đề cốt lõi dường như là thể loại trình thu gom rác được sử dụng trong thời gian chạy của ngôn ngữ. Tôi tìm thấy bài viết này , trong đó xóa một số quan niệm sai lầm cho tôi. Có vẻ như các gcs mạnh hơn chỉ xử lý bộ nhớ thô cấp thấp, điều này làm cho các loại đối tượng cấp cao hơn mờ đục đối với gc. Không có kiến ​​thức về bộ nhớ trong, gc không thể phá hủy các đối tượng. Mà dường như là kết luận của bạn.
dbcb

4

Mẫu trình hủy đối tượng là cơ bản để xử lý lỗi trong lập trình hệ thống, nhưng không liên quan gì đến việc thu gom rác. Thay vào đó, nó phải liên quan đến tuổi thọ đối tượng phù hợp với một phạm vi và có thể được thực hiện / sử dụng trong bất kỳ ngôn ngữ nào có chức năng hạng nhất.

Ví dụ (mã giả). Giả sử bạn có loại "tệp thô", như loại mô tả tệp Posix. Có bốn hoạt động cơ bản, open(), close(), read(), write(). Bạn muốn thực hiện một loại tệp "an toàn" luôn tự dọn sạch. (Tức là, có một hàm tạo và hàm hủy tự động.)

Tôi sẽ giả ngôn ngữ của chúng tôi đã xử lý ngoại lệ với throw, tryfinally(trong ngôn ngữ mà không xử lý, bạn có thể thiết lập một kỷ luật nơi người dùng của loại hình của bạn trả về một giá trị đặc biệt để chỉ ra một lỗi ngoại lệ.)

Bạn thiết lập một chức năng chấp nhận một chức năng thực hiện công việc. Hàm worker chấp nhận một đối số (xử lý tệp "an toàn").

with_file_opened_for_read (string:   filename,
                           function: worker_function(safe_file f)):
  raw_file rf = open(filename, O_RDONLY)
  if rf == error:
    throw File_Open_Error

  try:
    worker_function(rf)
  finally:
    close(rf)

Bạn cũng cung cấp các triển khai read()write()cho safe_file(chỉ cần gọi raw_file read()write()). Bây giờ người dùng sử dụng safe_fileloại như thế này:

...
with_file_opened_for_read ("myfile.txt",
                           anonymous_function(safe_file f):
                             mytext = read(f)
                             ... (including perhaps throwing an error)
                          )

Một hàm hủy C ++ thực sự chỉ là đường cú pháp cho một try-finallykhối. Gần như tất cả những gì tôi đã làm ở đây là chuyển đổi những gì một safe_filelớp C ++ với hàm tạo và hàm hủy sẽ biên dịch thành. Lưu ý rằng C ++ không có finallyngoại lệ, đặc biệt là vì Stroustrup cảm thấy rằng việc sử dụng một hàm hủy rõ ràng là tốt hơn về mặt cú pháp (và ông đã đưa nó vào ngôn ngữ trước khi ngôn ngữ có chức năng ẩn danh).

.


Điều này mô tả các phần bên trong của mẫu hàm hủy dựa trên ngăn xếp trong C ++, nhưng không giải thích được tại sao một ngôn ngữ được thu gom rác sẽ không thực hiện chức năng đó. Bạn có thể đúng rằng điều này không liên quan gì đến việc thu gom rác, nhưng nó có liên quan đến việc hủy / hoàn thiện đối tượng chung, điều này dường như khó khăn hoặc không hiệu quả trong các ngôn ngữ thu gom rác. Vì vậy, nếu sự hủy diệt chung không được hỗ trợ, thì sự hủy diệt dựa trên ngăn xếp dường như cũng bị bỏ qua.
dbcb

Như tôi đã nói lúc đầu: bất kỳ ngôn ngữ thu thập rác nào có chức năng hạng nhất (hoặc một số xấp xỉ chức năng hạng nhất) đều cung cấp cho bạn khả năng cung cấp các giao diện "chống đạn" safe_filewith_file_opened_for_read(một đối tượng tự đóng khi đi ra khỏi phạm vi ). Đó là điều quan trọng, rằng nó không có cú pháp giống như các nhà xây dựng là không liên quan. Lisp, Scheme, Java, Scala, Go, Haskell, Rust, Javascript, Clojure đều hỗ trợ đủ các chức năng hạng nhất, vì vậy chúng không cần các hàm hủy để cung cấp cùng một tính năng hữu ích.
Logic lang thang

Tôi nghĩ rằng tôi thấy những gì bạn đang nói. Vì các ngôn ngữ cung cấp các khối xây dựng cơ bản (thử / bắt / cuối cùng, các hàm hạng nhất, v.v.) để thực hiện thủ công chức năng giống như hàm hủy, chúng không cần các hàm hủy? Tôi có thể thấy một số ngôn ngữ đi theo con đường đó vì lý do đơn giản. Mặc dù, có vẻ như đó không phải là lý do chính cho tất cả các ngôn ngữ được liệt kê, nhưng có lẽ đó là những gì nó là. Có lẽ tôi chỉ là một nhóm thiểu số yêu thích các công cụ hủy diệt C ++ và không ai khác thực sự quan tâm, đó rất có thể là lý do tại sao hầu hết các ngôn ngữ không thực hiện các công cụ hủy diệt. Họ không quan tâm.
dbcb

2

Đây không phải là một câu trả lời đầy đủ cho câu hỏi, nhưng tôi muốn thêm một vài quan sát chưa được đề cập trong các câu trả lời hoặc nhận xét khác.

  1. Câu hỏi ngầm giả định rằng chúng ta đang nói về một ngôn ngữ hướng đối tượng theo kiểu Simula, chính nó là giới hạn. Trong hầu hết các ngôn ngữ, ngay cả những người có đối tượng, không phải mọi thứ đều là đối tượng. Các máy móc để thực hiện các công cụ hủy diệt sẽ áp đặt một chi phí mà không phải người thực hiện ngôn ngữ nào cũng sẵn sàng trả.

  2. C ++ có một số đảm bảo ngầm về trật tự hủy. Nếu bạn có cấu trúc dữ liệu giống như cây, chẳng hạn, con cái sẽ bị hủy trước cha mẹ. Đây không phải là trường hợp trong các ngôn ngữ của GC, vì vậy tài nguyên phân cấp có thể được phát hành theo thứ tự không thể đoán trước. Đối với tài nguyên không có bộ nhớ, điều này có thể quan trọng.


2

Khi hai khung công tác phổ biến nhất (Java và .NET) đang được thiết kế, tôi nghĩ rằng các tác giả dự kiến ​​rằng việc hoàn thiện sẽ hoạt động đủ tốt để tránh sự cần thiết phải có các hình thức quản lý tài nguyên khác. Nhiều khía cạnh của ngôn ngữ và thiết kế khung có thể được đơn giản hóa rất nhiều nếu không cần tất cả các tính năng cần thiết để đáp ứng quản lý tài nguyên xác định và đáng tin cậy 100%. Trong C ++, cần phân biệt giữa các khái niệm:

  1. Con trỏ / tham chiếu xác định một đối tượng được sở hữu độc quyền bởi chủ sở hữu của tham chiếu và không được xác định bởi bất kỳ con trỏ / tham chiếu nào mà chủ sở hữu không biết.

  2. Con trỏ / tham chiếu xác định một đối tượng có thể chia sẻ mà không phải ai sở hữu độc quyền.

  3. Con trỏ / tham chiếu xác định một đối tượng được sở hữu độc quyền bởi chủ sở hữu của tham chiếu, nhưng có thể truy cập được thông qua "lượt xem" mà chủ sở hữu không có cách theo dõi.

  4. Con trỏ / tham chiếu xác định một đối tượng cung cấp chế độ xem đối tượng thuộc sở hữu của người khác.

Nếu một ngôn ngữ / khung công tác GC không phải lo lắng về quản lý tài nguyên, tất cả những điều trên có thể được thay thế bằng một loại tham chiếu duy nhất.

Tôi sẽ thấy ngây thơ rằng ý tưởng quyết toán sẽ loại bỏ sự cần thiết của các hình thức quản lý tài nguyên khác, nhưng liệu kỳ vọng đó có hợp lý vào thời điểm đó hay không, lịch sử đã chỉ ra rằng có nhiều trường hợp yêu cầu quản lý tài nguyên chính xác hơn so với quyết toán . Tôi tình cờ nghĩ rằng phần thưởng của việc công nhận quyền sở hữu ở cấp độ ngôn ngữ / khung là đủ để biện minh cho chi phí (sự phức tạp phải tồn tại ở đâu đó và việc chuyển nó sang ngôn ngữ / khung sẽ đơn giản hóa mã người dùng) nhưng nhận ra rằng có ý nghĩa thiết kế lợi ích để có một "loại" tham chiếu duy nhất - thứ chỉ hoạt động nếu ngôn ngữ / khung không rõ ràng đối với các vấn đề về làm sạch tài nguyên.


2

Tại sao mô hình hủy diệt đối tượng trong các ngôn ngữ được thu thập rác lại vắng mặt?

Tôi đến từ một nền tảng C ++, vì vậy khu vực này gây trở ngại cho tôi.

Hàm hủy trong C ++ thực sự có hai thứ kết hợp. Nó giải phóng RAM và giải phóng id tài nguyên.

Các ngôn ngữ khác tách biệt những mối quan tâm này bằng cách yêu cầu GC chịu trách nhiệm giải phóng RAM trong khi một tính năng ngôn ngữ khác chịu trách nhiệm giải phóng id tài nguyên.

Tôi thấy rất thiếu khi các ngôn ngữ này coi bộ nhớ là tài nguyên duy nhất đáng để quản lý.

Đó là tất cả những gì về GC. Họ chỉ không làm một điều và đó là đảm bảo rằng bạn không hết bộ nhớ. Nếu RAM là vô hạn, tất cả các GC sẽ bị loại bỏ vì không còn lý do thực sự nào để chúng tồn tại.

Điều gì về ổ cắm, xử lý tập tin, trạng thái ứng dụng?

Các ngôn ngữ có thể cung cấp các cách khác nhau để giải phóng id tài nguyên bằng cách:

  • hướng dẫn sử dụng .CloseOrDispose()rải rác trên mã

  • hướng dẫn sử dụng .CloseOrDispose()rải rác trong " finallykhối" thủ công

  • dẫn sử dụng "khối id tài nguyên" (ví dụ using, with, try-với-nguồn lực , vv) mà tự động hóa .CloseOrDispose()sau khi khối được thực hiện

  • "khối id tài nguyên" được bảo đảm sẽ tự động hóa.CloseOrDispose() sau khi khối được hoàn thành

Nhiều ngôn ngữ sử dụng các cơ chế thủ công (trái ngược với bảo đảm) tạo ra cơ hội cho việc quản lý tài nguyên sai. Lấy mã NodeJS đơn giản này:

require('fs').openSync('file1.txt', 'w');
// forget to .closeSync the opened file

.. bất cứ nơi nào lập trình viên đã quên đóng tập tin đã mở.

Chừng nào chương trình tiếp tục chạy, tập tin đã mở sẽ bị kẹt trong tình trạng lấp lửng. Điều này rất dễ xác minh bằng cách thử mở tệp bằng HxD và xác minh rằng không thể thực hiện được:

nhập mô tả hình ảnh ở đây

Việc giải phóng id tài nguyên trong các hàm hủy C ++ cũng không được bảo đảm. Bạn có thể nghĩ RAII hoạt động giống như "khối id tài nguyên" được bảo đảm, nhưng không giống như "khối id tài nguyên", ngôn ngữ C ++ không ngăn đối tượng cung cấp khối RAII bị rò rỉ , do đó, khối RAII có thể không bao giờ được thực hiện .


Dường như gần như tất cả các ngôn ngữ được thu thập rác hiện đại có hỗ trợ đối tượng OOPy như Ruby, Javascript / ES6 / ES7, Actioncript, Lua, v.v ... hoàn toàn bỏ qua mô hình hủy diệt / hoàn thiện. Python dường như là người duy nhất có __del__()phương thức lớp của nó . Tại sao lại thế này?

Bởi vì họ quản lý id tài nguyên bằng các cách khác, như đã đề cập ở trên.

Các quyết định thiết kế ngôn ngữ dẫn đến các ngôn ngữ này không có cách nào để thực thi logic tùy chỉnh khi xử lý đối tượng?

Bởi vì họ quản lý id tài nguyên bằng các cách khác, như đã đề cập ở trên.

Tại sao một ngôn ngữ sẽ có khái niệm tích hợp của các thể hiện đối tượng với các cấu trúc giống như lớp hoặc lớp cùng với khởi tạo tùy chỉnh (hàm tạo), nhưng hoàn toàn bỏ qua chức năng hủy / hoàn thiện?

Bởi vì họ quản lý id tài nguyên bằng các cách khác, như đã đề cập ở trên.

Tôi có thể thấy một đối số có thể là bộ hủy / bộ hoàn thiện có thể không được gọi cho đến khi một thời gian không xác định trong tương lai, nhưng điều đó không ngăn Java hoặc Python hỗ trợ chức năng.

Java không có hàm hủy.

Các tài liệu Java đề cập đến :

tuy nhiên, mục đích thông thường của việc hoàn thiện 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 đại diện cho 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.

..nhưng đặt mã quản lý tài nguyên-id bên trong Object.finalizerphần lớn được coi là một mô hình chống ( xem ). Những mã đó thay vào đó nên được viết tại trang web cuộc gọi.

Đối với những người sử dụng mô hình chống, lý do của họ là họ có thể đã quên phát hành id tài nguyên tại trang web cuộc gọi. Vì vậy, họ làm điều đó một lần nữa trong quyết toán, chỉ trong trường hợp.

Các lý do thiết kế ngôn ngữ cốt lõi để không hỗ trợ bất kỳ hình thức hoàn thiện đối tượng là gì?

Không có nhiều trường hợp sử dụng cho các công cụ hoàn thiện vì chúng là để chạy một đoạn mã trong khoảng thời gian khi không còn bất kỳ tham chiếu mạnh nào đến đối tượng và thời điểm mà bộ nhớ của nó được thu hồi bởi GC.

Trường hợp sử dụng có thể là khi bạn muốn giữ một bản ghi thời gian giữa các đối tượng được thu thập bởi GC và thời gian khi không còn bất kỳ tham chiếu mạnh nào đến đối tượng, như vậy:

finalize() {
    Log(TimeNow() + ". Obj " + toString() + " is going to be memory-collected soon!"); // "soon"
}

-1

đã tìm thấy một tài liệu tham khảo về vấn đề này trong Tiến sĩ Dobbs wrt c ++ có nhiều ý tưởng chung hơn cho rằng các kẻ hủy diệt đang gặp vấn đề trong một ngôn ngữ nơi chúng được thực hiện. một ý tưởng sơ bộ ở đây dường như là một mục đích chính của các tàu khu trục là để xử lý việc giải quyết bộ nhớ và khó có thể thực hiện chính xác. bộ nhớ được phân bổ từng phần nhưng các đối tượng khác nhau được kết nối và sau đó trách nhiệm / ranh giới phân bổ không quá rõ ràng.

Vì vậy, giải pháp cho vấn đề này của trình thu gom rác đã phát triển từ nhiều năm trước, nhưng việc thu gom rác không dựa trên các đối tượng biến mất khỏi phạm vi khi thoát khỏi phương thức (đó là một ý tưởng khái niệm khó thực hiện), nhưng trên một trình thu thập chạy định kỳ, hơi không rõ ràng, khi ứng dụng gặp "áp lực bộ nhớ" (tức là hết bộ nhớ).

nói cách khác, khái niệm con người đơn thuần về một "đối tượng mới chưa được sử dụng" thực sự theo một cách nào đó là sự trừu tượng sai lệch theo nghĩa là không đối tượng nào có thể "ngay lập tức" trở nên không được sử dụng. các đối tượng không sử dụng chỉ có thể được "phát hiện" bằng cách chạy thuật toán thu gom rác đi ngang qua biểu đồ tham chiếu đối tượng và các thuật toán hoạt động tốt nhất chạy không liên tục.

Có thể một thuật toán thu gom rác tốt hơn đang chờ được phát hiện có thể xác định gần như ngay lập tức các đối tượng không sử dụng, sau đó có thể dẫn đến mã gọi hủy diệt nhất quán, nhưng người ta đã không tìm thấy sau nhiều năm nghiên cứu trong khu vực.

giải pháp cho các lĩnh vực quản lý tài nguyên như tệp hoặc kết nối dường như có các "người quản lý" đối tượng cố gắng xử lý việc sử dụng của họ.


2
Thú vị tìm thấy. Cảm ơn. Đối số của tác giả dựa trên hàm hủy được gọi không đúng lúc do chuyển các thể hiện của lớp theo giá trị trong đó lớp không có hàm tạo sao chép thích hợp (đây là một vấn đề thực sự). Tuy nhiên, kịch bản này không thực sự tồn tại trong hầu hết (nếu không phải tất cả) các ngôn ngữ động hiện đại bởi vì mọi thứ đều được thông qua tham chiếu để tránh tình trạng của tác giả. Mặc dù đây là một viễn cảnh thú vị, tôi không nghĩ nó giải thích tại sao hầu hết các ngôn ngữ được thu gom rác đã chọn bỏ qua chức năng hủy / hủy.
dbcb

2
Câu trả lời này trình bày sai về bài báo của Tiến sĩ Dobb: bài báo không cho rằng các kẻ hủy diệt nói chung là có vấn đề. Bài báo thực sự lập luận điều này: Các nguyên thủy quản lý bộ nhớ giống như các câu lệnh goto, bởi vì chúng đều đơn giản nhưng quá mạnh mẽ. Theo cùng cách mà các câu lệnh goto được gói gọn trong "cấu trúc điều khiển giới hạn phù hợp" (Xem: Dijktsra), các nguyên hàm quản lý bộ nhớ được gói gọn trong "cấu trúc dữ liệu giới hạn phù hợp". Phá hủy là một bước theo hướng này, nhưng không đủ xa. Quyết định cho chính mình cho dù điều đó có đúng hay không.
kdbanman
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.