C ++ xóa so với Java GC


8

Bộ sưu tập rác Java chăm sóc các đối tượng chết trên đống, nhưng đôi khi đóng băng thế giới. Trong C ++, tôi phải gọi deleteđể loại bỏ một đối tượng được tạo vào cuối vòng đời của nó.

Đây deletecó vẻ như là một mức giá rất thấp để trả cho môi trường không đóng băng. Đặt tất cả các deletetừ khóa có liên quan là một nhiệm vụ cơ học. Người ta có thể viết một tập lệnh sẽ đi qua mã và xóa địa điểm một khi không có nhánh mới nào sử dụng một đối tượng nhất định.

Vì vậy, những ưu và nhược điểm của Java build trong mô hình thu gom rác tự làm vs C ++.


Tôi không muốn bắt đầu một chủ đề C ++ và Java. Câu hỏi của tôi là khác nhau.
Toàn bộ điều này của GC - có phải là "hãy gọn gàng, đừng quên xóa các đối tượng bạn đã tạo - và bạn sẽ không cần bất kỳ GC chuyên dụng nào? Hay nó giống như" xử lý các đối tượng trong C ++ thực sự khó khăn - Tôi dành 20% thời gian của tôi cho nó và tuy nhiên, rò rỉ bộ nhớ là một nơi phổ biến "?


15
Tôi thấy khó tin rằng kịch bản ma thuật của bạn có thể tồn tại. Ngoài bất cứ điều gì, nó sẽ không phải là một giải pháp cho vấn đề tạm dừng. Hoặc cách khác (và nhiều khả năng) chỉ hoạt động cho các chương trình cực kỳ đơn giản
Richard Tingle

1
Ngoài ra java hiện đại không bị đóng băng ngoại trừ trong điều kiện khắc nghiệt nơi tạo ra một lượng rác khổng lồ
Richard Tingle

1
@ThomasCarleway cũng có thể - và đôi khi không - ảnh hưởng đến hiệu suất. Nhưng có rất nhiều tham số để điều chỉnh trong những trường hợp này, và đôi khi giải pháp có thể là chuyển sang một gc khác hoàn toàn. Tất cả phụ thuộc vào lượng tài nguyên có sẵn và tải trọng điển hình.
Hulk

4
" Đặt tất cả các từ khóa xóa có liên quan là một nhiệm vụ cơ học " - Đó là lý do tại sao có các công cụ để phát hiện rò rỉ bộ nhớ. Bởi vì nó rất đơn giản và không dễ bị lỗi.
JensG

2
@Doval, nếu bạn đang sử dụng RAII một cách chính xác, điều này khá đơn giản, hầu như không có sổ sách kế toán nào cả. newđi vào hàm tạo của lớp bạn quản lý bộ nhớ, deleteđi vào hàm hủy. Từ đó, tất cả đều tự động (lưu trữ). Lưu ý rằng điều này hoạt động cho tất cả các loại tài nguyên, không chỉ bộ nhớ, không giống như bộ sưu tập rác. Mutexes được thực hiện trong một constructor, được phát hành trong một hàm hủy. Các tệp được mở trong một hàm tạo, đóng trong một hàm hủy.
Rob K

Câu trả lời:


18

Vòng đời đối tượng C ++

Nếu bạn tạo các đối tượng cục bộ, bạn không cần xóa chúng: trình biên dịch tạo mã để tự động xóa chúng khi đối tượng nằm ngoài phạm vi

Nếu bạn sử dụng con trỏ đối tượng và tạo đối tượng trên cửa hàng miễn phí, thì bạn phải cẩn thận xóa đối tượng khi không còn cần thiết (như bạn đã mô tả). Thật không may, trong phần mềm phức tạp, điều này có thể khó khăn hơn nhiều so với vẻ ngoài của nó (ví dụ: nếu một ngoại lệ được nêu ra và phần xóa dự kiến ​​sẽ không bao giờ đạt được?).

May mắn thay, trong C ++ hiện đại (còn gọi là C ++ 11 trở lên), bạn có con trỏ thông minh, chẳng hạn như shared_ptr. Họ tham chiếu - đếm đối tượng được tạo theo cách rất hiệu quả, giống như một trình thu gom rác sẽ làm trong Java. Và ngay khi đối tượng không còn được tham chiếu nữa, hoạt động cuối cùng shared_ptrsẽ xóa đối tượng cho bạn. Tự động. Giống như trình thu gom rác, nhưng một đối tượng tại một thời điểm và không chậm trễ (Ok: bạn cần một số chăm sóc thêm và weak_ptrđể đối phó với các tham chiếu vòng tròn).

Kết luận: ngày nay bạn có thể viết mã C ++ mà không phải lo lắng về việc cấp phát bộ nhớ và không bị rò rỉ như với một GC, nhưng không có hiệu ứng đóng băng.

Vòng đời đối tượng Java

Điều tuyệt vời là bạn không phải lo lắng về vòng đời của đối tượng. Bạn chỉ cần tạo chúng và java sẽ chăm sóc phần còn lại. Một GC hiện đại sẽ xác định và phá hủy các đối tượng không còn cần thiết (bao gồm cả nếu có các tham chiếu vòng tròn giữa các đối tượng chết).

Thật không may, do sự thoải mái này, bạn không có quyền kiểm soát thực sự khi đối tượng thực sự bị xóa . Về mặt ngữ nghĩa, việc xóa / hủy trùng khớp với bộ sưu tập rác .

Điều này là hoàn toàn tốt nếu nhìn vào các đối tượng chỉ về mặt bộ nhớ. Ngoại trừ việc đóng băng, nhưng đây không phải là một trường hợp tử vong (mọi người đang làm việc này). Tôi không phải là chuyên gia Java, nhưng tôi nghĩ rằng việc phá hủy bị trì hoãn khiến việc xác định rò rỉ trong java trở nên khó khăn hơn do các tài liệu tham khảo vô tình được giữ mặc dù các đối tượng không còn cần thiết nữa (tức là bạn không thể thực sự theo dõi việc xóa các đối tượng).

Nhưng điều gì sẽ xảy ra nếu đối tượng phải kiểm soát các tài nguyên khác ngoài bộ nhớ, ví dụ như một tệp đang mở, một semaphore, một dịch vụ hệ thống? Lớp của bạn phải cung cấp một phương thức để giải phóng các tài nguyên này. Và bạn sẽ có trách nhiệm đảm bảo rằng phương thức này được gọi khi tài nguyên không còn cần thiết nữa. Trong mọi đường dẫn phân nhánh có thể thông qua mã của bạn, đảm bảo nó cũng được gọi trong trường hợp ngoại lệ. Thách thức rất giống với việc xóa rõ ràng trong C ++.

Kết luận: GC giải quyết vấn đề quản lý bộ nhớ. Nhưng nó không giải quyết việc quản lý các tài nguyên hệ thống khác. Việc không xóa "chỉ trong thời gian" có thể khiến việc quản lý tài nguyên trở nên rất khó khăn.

Xóa, thu gom rác và RAII

Khi bạn có thể kiểm soát việc xóa một đối tượng và hàm hủy sẽ được gọi khi xóa, bạn có thể hưởng lợi từ RAII . Cách tiếp cận này chỉ xem bộ nhớ như một trường hợp đặc biệt của phân bổ tài nguyên và liên kết quản lý tài nguyên an toàn hơn với vòng đời đối tượng, do đó đảm bảo việc sử dụng tài nguyên được kiểm soát chặt chẽ.


3
Vẻ đẹp của những người thu gom rác (hiện đại) là bạn không thực sự cần phải suy nghĩ về các tài liệu tham khảo theo chu kỳ. Các nhóm Đối tượng như vậy không thể truy cập được ngoại trừ nhau được phát hiện và thu thập. Đó là một lợi thế rất lớn so với đếm tham chiếu đơn giản / con trỏ thông minh.
Hulk

6
+1 Phần "ngoại lệ" không thể được nhấn mạnh đủ. Sự hiện diện của các ngoại lệ làm cho nhiệm vụ khó khăn và vô nghĩa của quản lý bộ nhớ thủ công hầu như không thể, và do đó quản lý bộ nhớ thủ công không được sử dụng trong C ++. Sử dụng RAII. Không sử dụng newbên ngoài các hàm tạo / con trỏ thông minh và không bao giờ sử dụng deletebên ngoài hàm hủy.
Felix Dombek

@Hulk Bạn có một điểm ở đây! Mặc dù hầu hết các lập luận của tôi vẫn còn hiệu lực, nhưng GC đã có nhiều tiến bộ. Và các tham chiếu tròn thực sự rất khó xử lý với tham chiếu đếm con trỏ thông minh một mình. Vì vậy, tôi đã chỉnh sửa câu trả lời của mình cho phù hợp, để giữ cân bằng tốt hơn. Tôi cũng đã thêm một tài liệu tham khảo cho một bài viết về các chiến lược có thể để giảm thiểu hiệu ứng đóng băng.
Christophe

3
+1 Việc quản lý các tài nguyên không phải là bộ nhớ thực sự đòi hỏi một số nỗ lực bổ sung cho các ngôn ngữ GC vì RAII không hoạt động nếu không có sự kiểm soát chi tiết nào về việc xóa (hoặc nếu) xóa / hoàn thiện đối tượng happpens. Các cấu trúc đặc biệt có sẵn trong hầu hết các ngôn ngữ này ( ví dụ, xem tài nguyên thử của java ), có thể, kết hợp với các cảnh báo của trình biên dịch, giúp làm cho những điều này trở nên đúng đắn.
Hulk

1
Like garbage collector, but one object at a time and without delay (Ok: you need some extra care and weak_ptr to cope with circular references).Số tham chiếu có thể xếp tầng mặc dù. Ví dụ: Tham chiếu cuối cùng Abiến mất, nhưng Acũng có tham chiếu cuối cùng B, người có tham chiếu cuối cùng để C... And you'll have the responsibility to make sure that this method is called when the resources are no longer needed. In every possible branching path through your code, ensuring it is also invoked in case of exceptions.Java và C # có các câu lệnh chặn đặc biệt cho điều này.
Doval

5

Xóa này có vẻ như là một mức giá rất thấp để trả cho môi trường không đóng băng. Đặt tất cả các từ khóa xóa có liên quan là một nhiệm vụ cơ học. Người ta có thể viết một tập lệnh sẽ đi qua mã và xóa địa điểm một khi không có nhánh mới nào sử dụng một đối tượng nhất định.

Nếu bạn có thể viết một kịch bản như vậy, xin chúc mừng. Bạn là một nhà phát triển tốt hơn tôi. Cho đến nay.

Cách duy nhất bạn thực sự có thể tránh rò rỉ bộ nhớ trong các trường hợp thực tế là các tiêu chuẩn mã hóa rất nghiêm ngặt với các quy tắc rất nghiêm ngặt là chủ sở hữu của một đối tượng và khi nào nó có thể và phải được phát hành, hoặc các công cụ như con trỏ thông minh đếm các tham chiếu đến các đối tượng và xóa các đối tượng khi tham chiếu cuối cùng đã biến mất.


4
Đúng, thực hiện quản lý tài nguyên thủ công luôn dễ bị lỗi, may mắn là nó chỉ không cần thiết trong Java (đối với tài nguyên không có bộ nhớ), vì C ++ có RAII.
Ded repeatator

Cách duy nhất bạn thực sự có thể tránh BẤT K rò rò rỉ tài nguyên trong các trường hợp thực tế là các tiêu chuẩn mã hóa rất nghiêm ngặt với các quy tắc rất nghiêm ngặt là chủ sở hữu của một đối tượng ... bộ nhớ không phải là tài nguyên duy nhất và trong hầu hết các trường hợp, không phải là tài nguyên quan trọng nhất hoặc.
tò mò

2
Nếu bạn coi RAII là 'tiêu chuẩn mã hóa rất nghiêm ngặt'. Tôi coi đó là một 'hố thành công', dễ sử dụng.
Rob K

5

Nếu bạn viết mã C ++ chính xác bằng RAII, bạn thường không viết bất kỳ mới hoặc xóa. "Mới" duy nhất bạn viết nằm trong các con trỏ được chia sẻ để bạn thực sự không bao giờ phải sử dụng "xóa".


1
Bạn hoàn toàn không nên sử dụng new, ngay cả với các con trỏ được chia sẻ - std::make_sharedthay vào đó bạn nên sử dụng .
Jules

1
hoặc tốt hơn , make_unique. Nó thực sự khá hiếm khi bạn thực sự cần sở hữu chung.
Marc

4

Làm cho cuộc sống của các lập trình viên dễ dàng hơn và ngăn chặn rò rỉ bộ nhớ là một lợi thế quan trọng của việc thu gom rác nhưng nó không phải là duy nhất. Một cách khác là ngăn chặn sự phân mảnh bộ nhớ. Trong C ++, một khi bạn phân bổ một đối tượng bằng newtừ khóa, nó sẽ ở một vị trí cố định trong bộ nhớ. Điều này có nghĩa là, khi ứng dụng chạy, cuối cùng bạn sẽ có những khoảng trống của bộ nhớ trống ở giữa các đối tượng được phân bổ. Vì vậy, việc phân bổ bộ nhớ trong C ++ nhất thiết phải là một quá trình phức tạp hơn, vì hệ điều hành cần có khả năng tìm thấy các khối chưa được phân bổ có kích thước nhất định phù hợp giữa các khoảng trống.

Bộ sưu tập rác sẽ chăm sóc nó bằng cách lấy tất cả các đối tượng không bị xóa và dịch chuyển chúng trong bộ nhớ để chúng tạo thành một khối liên tục. Nếu bạn trải nghiệm việc thu gom rác mất một chút thời gian, đó có thể là do quá trình này, không phải do sự phân bổ bộ nhớ. Lợi ích của nó là khi phân bổ bộ nhớ, nó gần như đơn giản như chuyển một con trỏ đến cuối ngăn xếp.

Vì vậy, trong C ++ xóa các đối tượng là nhanh nhưng việc tạo chúng có thể chậm. Trong Java, việc tạo các đối tượng hoàn toàn không mất thời gian nhưng thỉnh thoảng bạn cần thực hiện một số công việc dọn phòng.


4
Có, việc phân bổ từ cửa hàng miễn phí trong C ++ chậm hơn so với Java. May mắn thay, nó ít thường xuyên hơn và bạn có thể dễ dàng sử dụng các công cụ phân bổ cho mục đích đặc biệt theo ý của mình khi bạn có bất kỳ mẫu bất thường nào. Ngoài ra, tất cả các tài nguyên đều bằng nhau trong C ++, Java có các trường hợp đặc biệt.
Ded repeatator

3
Trong 20 năm mã hóa trong C ++, tôi chưa bao giờ thấy sự phân mảnh bộ nhớ trở thành một vấn đề. Các hệ điều hành hiện đại với quản lý bộ nhớ ảo trên các bộ xử lý có nhiều mức bộ nhớ cache đã loại bỏ phần lớn vấn đề này.
Rob K

@RobK - "Các hệ điều hành hiện đại có quản lý bộ nhớ ảo trên các bộ xử lý có nhiều mức bộ nhớ cache đã loại bỏ phần lớn [phân mảnh] thành một vấn đề" - không có chúng. Bạn có thể không nhận thấy điều đó, nhưng nó vẫn xảy ra, nó vẫn gây ra (1) lãng phí bộ nhớ và (2) sử dụng bộ nhớ cache kém hiệu quả hơn và các giải pháp khả thi duy nhất là sao chép GC hoặc thiết kế quản lý bộ nhớ thủ công rất cẩn thận để đảm bảo nó không Sẽ xảy ra.
Jules

3

Những lời hứa chính của Java là

  1. Cú pháp C dễ hiểu
  2. Viết một lần chạy khắp nơi
  3. Chúng tôi làm cho công việc của bạn dễ dàng hơn - chúng tôi thậm chí còn chăm sóc rác.

Có vẻ như Java đảm bảo với bạn rằng rác sẽ được xử lý (không nhất thiết phải theo cách hiệu quả). Nếu bạn sử dụng C / C ++, bạn có cả tự do và trách nhiệm. Bạn có thể làm điều đó tốt hơn so với GC của Java hoặc bạn có thể tệ hơn nhiều (bỏ qua deletetất cả cùng nhau và có vấn đề rò rỉ bộ nhớ).

Nếu bạn cần mã "đáp ứng các tiêu chuẩn chất lượng nhất định" và để tối ưu hóa "tỷ lệ giá / chất lượng", hãy sử dụng Java. Nếu bạn sẵn sàng đầu tư thêm tài nguyên (thời gian của các chuyên gia) để cải thiện hiệu suất của ứng dụng quan trọng - hãy sử dụng C.


Vâng, tất cả các lời hứa có thể được coi là bị phá vỡ, dễ dàng.
Ded repeatator

Ngoại trừ việc Java rác duy nhất sẽ thu thập được là bộ nhớ được cấp phát động. Nó không làm gì về bất kỳ tài nguyên được phân bổ động nào khác.
Rob K

@RobK - điều đó không hoàn toàn đúng. Việc sử dụng hoàn thiện đối tượng có thể xử lý việc phân bổ các tài nguyên khác. Điều này được khuyến khích rộng rãi bởi vì trong hầu hết các trường hợp, bạn không muốn điều đó (không giống như bộ nhớ, hầu hết các loại tài nguyên khác bị hạn chế hơn hoặc thậm chí độc đáo và do đó việc phân bổ chính xác là rất quan trọng), nhưng có thể được thực hiện. Java cũng có câu lệnh try-with-resource có thể được sử dụng để tự động hóa việc quản lý các tài nguyên khác (cung cấp các lợi ích tương tự cho RAII).
Jules

@Jules: Một điểm khác biệt chính là trong các đối tượng C ++ tự hủy. Trong Java Các đối tượng có thể đóng lại không thể tự đóng. Một nhà phát triển Java viết "new Something (...)" hoặc (tệ hơn) Something s = someFactory.create (...) phải kiểm tra xem liệu Something có thể đóng được không. Thay đổi một lớp từ không thể đóng thành có thể đóng là loại thay đổi phá vỡ tồi tệ nhất, và vì vậy thực tế không bao giờ có thể được thực hiện. Không quá tệ ở cấp độ lớp, nhưng một vấn đề nghiêm trọng khi một người đang xác định giao diện Java.
kevin cline

2

Sự khác biệt lớn mà bộ sưu tập rác tạo ra không phải là bạn không phải xóa các đối tượng một cách rõ ràng. Sự khác biệt lớn hơn nhiều là bạn không phải sao chép các đối tượng.

Điều này có tác dụng trở nên phổ biến trong việc thiết kế các chương trình và giao diện nói chung. Hãy để tôi chỉ đưa ra một ví dụ nhỏ để cho thấy mức độ sâu rộng của nó.

Trong Java, khi bạn bật một cái gì đó từ ngăn xếp, giá trị được bật sẽ được trả về, do đó bạn nhận được mã như thế này:

WhateverType value = myStack.Pop();

Trong Java, điều này là ngoại lệ an toàn, bởi vì tất cả những gì chúng ta thực sự đang làm là sao chép một tham chiếu đến một đối tượng, điều này được đảm bảo xảy ra mà không có ngoại lệ. Điều tương tự không đúng trong C ++. Trong C ++, trả về một giá trị có nghĩa là (hoặc ít nhất có thể có nghĩa là) sao chép giá trị đó và với một số loại có thể ném ngoại lệ. Nếu ngoại lệ được ném sau khi mục bị xóa khỏi ngăn xếp, nhưng trước khi bản sao được gửi đến người nhận, mục đó đã bị rò rỉ. Để chứng minh điều đó, ngăn xếp của C ++ sử dụng một cách tiếp cận có phần vụng về hơn trong đó lấy ra mục trên cùng và loại bỏ mục trên cùng là hai thao tác riêng biệt:

WhateverType value = myStack.top();
myStack.pop();

Nếu câu lệnh đầu tiên ném một ngoại lệ, thì câu lệnh thứ hai sẽ không được thực thi, vì vậy nếu một ngoại lệ được ném vào sao chép, mục này vẫn nằm trên ngăn xếp như thể không có gì xảy ra cả.

Vấn đề rõ ràng là điều này đơn giản là vụng về và (đối với những người chưa sử dụng nó) bất ngờ.

Điều tương tự cũng đúng ở nhiều phần khác của C ++: đặc biệt là trong mã chung, an toàn ngoại lệ bao trùm nhiều phần của thiết kế - và điều này phần lớn là do thực tế là hầu hết có khả năng liên quan đến việc sao chép các đối tượng (có thể ném), trong đó Java sẽ chỉ tạo các tham chiếu mới cho các đối tượng hiện có (không thể ném, vì vậy chúng tôi không phải lo lắng về các ngoại lệ).

Theo như một kịch bản đơn giản để chèn delete nơi cần thiết: nếu bạn có thể xác định tĩnh khi nào cần xóa các mục dựa trên cấu trúc của mã nguồn, thì có lẽ không nên sử dụng newdeleteở vị trí đầu tiên.

Để tôi cho bạn một ví dụ về một chương trình mà điều này gần như chắc chắn sẽ không thể thực hiện được: một hệ thống để thực hiện, theo dõi, thanh toán (v.v.) các cuộc gọi điện thoại. Khi bạn quay số điện thoại của mình, nó sẽ tạo ra một đối tượng "gọi". Đối tượng cuộc gọi theo dõi những người bạn đã gọi, thời gian bạn nói chuyện với họ, v.v., để thêm các bản ghi thích hợp vào nhật ký thanh toán. Đối tượng cuộc gọi theo dõi trạng thái phần cứng, vì vậy khi bạn gác máy, nó sẽ tự hủy (sử dụng thảo luận rộng rãi delete this;). Chỉ là, nó không thực sự tầm thường như "khi bạn gác máy". Ví dụ: bạn có thể bắt đầu cuộc gọi hội nghị, kết nối hai người và gác máy - nhưng cuộc gọi vẫn tiếp tục giữa hai bên ngay cả sau khi bạn gác máy (nhưng thanh toán có thể thay đổi).


"Nếu ngoại lệ được ném sau khi mục bị xóa khỏi ngăn xếp, nhưng trước khi bản sao đến người nhận, mục đó đã bị rò rỉ" bạn có tham khảo nào cho việc này không? Bởi vì điều này nghe có vẻ kỳ lạ mặc dù tôi không phải là chuyên gia về c ++.
Esben Skov Pedersen

@EsbenSkovPedersen: GoTW # 8 sẽ là điểm khởi đầu hợp lý. Nếu bạn có quyền truy cập vào nó, C ++ đặc biệt có nhiều hơn một chút. Lưu ý rằng cả hai đều mong đợi ít nhất một số kiến thức đã có từ trước về C ++.
Jerry Coffin

Điều đó dường như đủ thẳng về phía trước. Đây thực sự là câu này làm tôi bối rối "Trong C ++, trả về một giá trị có nghĩa là (hoặc ít nhất có thể có nghĩa là) sao chép giá trị đó và với một số loại có thể ném ngoại lệ" Đây có phải là bản sao trên heap hay stack không?
Esben Skov Pedersen

Bạn tuyệt đối không phải sao chép các đối tượng trong C ++, nhưng bạn có thể nếu bạn muốn, không giống như các ngôn ngữ được thu gom rác (Java, C #) làm cho nó trở thành PITA để sao chép một đối tượng khi bạn muốn. 90% các đối tượng tôi tạo nên bị phá hủy và tài nguyên của chúng được giải phóng khi chúng vượt ra khỏi phạm vi. Để buộc tất cả các đối tượng vào lưu trữ động bởi vì 10% trong số chúng cần phải có vẻ ngu ngốc nhất.
Rob K

2
Tuy nhiên, đây thực sự không phải là một sự khác biệt do việc sử dụng bộ sưu tập rác, nhưng bởi triết lý "tất cả các đối tượng là tài liệu tham khảo" được đơn giản hóa của Java. Hãy xem C # như một ví dụ ngược lại: đó ngôn ngữ được thu gom rác, nhưng cũng có các đối tượng giá trị ("cấu trúc" trong thuật ngữ địa phương, khác với cấu trúc C ++) có sao chép ngữ nghĩa. C # tránh được vấn đề bởi (1) có sự phân tách rõ ràng giữa các loại giá trị tham chiếu và giá trị và (2) luôn sao chép các loại giá trị bằng cách sao chép theo byte, không phải mã người dùng, do đó ngăn ngừa ngoại lệ trong khi sao chép.
Jules

2

Một cái gì đó tôi không nghĩ đã được đề cập ở đây là có hiệu quả đến từ việc thu gom rác. Trong các trình thu thập Java được sử dụng phổ biến nhất, vị trí chính mà các đối tượng được phân bổ là một khu vực dành riêng cho một trình thu thập sao chép. Khi mọi thứ bắt đầu, không gian này trống rỗng. Khi các đối tượng được tạo, chúng được phân bổ cạnh nhau trong không gian mở lớn cho đến khi nó không thể phân bổ một trong không gian tiếp giáp còn lại. GC đá vào và tìm kiếm bất kỳ vật thể nào trong không gian này không chết. Nó sao chép các đối tượng sống sang một khu vực khác và đặt chúng lại với nhau (nghĩa là không bị phân mảnh.) Không gian cũ được coi là sạch sẽ. Sau đó nó tiếp tục phân bổ các đối tượng chặt chẽ với nhau và lặp lại quá trình này khi cần thiết.

Có hai lợi ích cho việc này. Đầu tiên là không có thời gian dành cho việc xóa các đối tượng không sử dụng. Một khi các đối tượng sống được sao chép, đá phiến được coi là sạch và các đối tượng chết đơn giản là bị lãng quên. Trong nhiều ứng dụng, hầu hết các đối tượng không sống quá lâu nên chi phí sao chép bộ trực tiếp là rẻ so với tiết kiệm có được do không phải lo lắng về bộ chết.

Lợi ích thứ hai là khi một đối tượng mới được phân bổ, không cần phải tìm kiếm một khu vực tiếp giáp. VM luôn biết đối tượng tiếp theo sẽ được đặt ở đâu (báo trước: đơn giản hóa bỏ qua đồng thời.)

Loại thu thập và phân bổ này là rất nhanh. Từ góc độ thông lượng tổng thể, thật khó để đánh bại trong nhiều kịch bản. Vấn đề là một số đối tượng sẽ sống lâu hơn bạn muốn sao chép chúng xung quanh và cuối cùng điều đó có nghĩa là người sưu tầm có thể cần phải tạm dừng một khoảng thời gian đáng kể mỗi lần và khi điều đó xảy ra có thể không dự đoán được. Tùy thuộc vào độ dài của tạm dừng và loại ứng dụng, điều này có thể hoặc không thể là một vấn đề. Có ít nhất một bộ sưu tập không ngừng . Tôi hy vọng sẽ có sự đánh đổi hiệu quả thấp hơn để có được bản chất không ngừng nghỉ, nhưng một trong những người thành lập công ty đó (Gil Tene) là một chuyên gia uber tại GC và các bài thuyết trình của anh ấy là một nguồn thông tin tuyệt vời về GC.


Azul được thành lập bởi một nhóm các anh chàng GC, những người biên dịch, những người thực hiện và các cựu CPU Java từ Sun. Họ biết những gì họ đang làm.
Jörg W Mittag

@ JörgWMittag được cập nhật để phản ánh có nhiều hơn một người sáng lập. cảm ơn
JimmyJames

1

Hay nó giống như "việc xử lý các đối tượng trong C ++ thực sự rất khó khăn - tôi dành 20% thời gian cho nó và tuy nhiên, rò rỉ bộ nhớ là một nơi phổ biến"?

Theo kinh nghiệm cá nhân của tôi về C ++ và thậm chí C, rò rỉ bộ nhớ chưa bao giờ là một cuộc đấu tranh lớn để tránh. Ví dụ, với quy trình kiểm tra lành mạnh và Valgrind, mọi rò rỉ vật lý do cuộc gọi đến operator new/mallocmà không có tương ứng delete/freethường được phát hiện và sửa chữa nhanh chóng. Công bằng mà nói, một số cơ sở mã C ++ lớn hoặc trường học cũ rất có thể có một số trường hợp cạnh khó hiểu có thể rò rỉ vật lý một số byte bộ nhớ ở đây và do đó không phải deleting/freeinglà trường hợp cạnh đó đã bay theo radar thử nghiệm.

Tuy nhiên, theo như những quan sát thực tế, các ứng dụng rò rỉ nhất mà tôi gặp phải (như trong những ứng dụng tiêu thụ càng nhiều bộ nhớ thì bạn càng chạy chúng lâu hơn, mặc dù lượng dữ liệu chúng tôi làm việc không tăng lên) thường không được viết bằng C hoặc C ++. Tôi không tìm thấy những thứ như Linux Kernel hoặc Unreal Engine hoặc thậm chí mã gốc được sử dụng để triển khai Java trong danh sách các phần mềm bị rò rỉ mà tôi gặp phải.

Loại phần mềm rò rỉ nổi bật nhất mà tôi có xu hướng gặp phải là những thứ như Flash applet, như trò chơi Flash, mặc dù chúng sử dụng bộ sưu tập rác. Và đó không phải là một so sánh công bằng nếu người ta suy luận bất cứ điều gì từ điều này vì nhiều ứng dụng Flash được viết bởi các nhà phát triển vừa chớm nở, những người có thể thiếu các nguyên tắc kỹ thuật âm thanh và quy trình thử nghiệm (và tương tự như vậy, tôi chắc chắn có những chuyên gia lành nghề làm việc với GC không đấu tranh với phần mềm bị rò rỉ), nhưng tôi sẽ có rất nhiều điều để nói với bất cứ ai nghĩ rằng GC ngăn chặn phần mềm bị rò rỉ được viết.

Con trỏ

Bây giờ đến từ miền, kinh nghiệm cụ thể của tôi và vì hầu hết sử dụng C và C ++ (và tôi hy vọng lợi ích của GC sẽ thay đổi tùy thuộc vào kinh nghiệm và nhu cầu của chúng tôi), điều ngay lập tức nhất mà GC giải quyết cho tôi không phải là vấn đề rò rỉ bộ nhớ thực tế mà là truy cập con trỏ lơ lửng, và đó thực sự có thể là cứu cánh trong các tình huống quan trọng.

Thật không may trong nhiều trường hợp trong đó GC giải quyết điều gì khác sẽ là truy cập con trỏ lơ lửng, nó thay thế cùng một loại lỗi lập trình viên bằng rò rỉ bộ nhớ logic.

Nếu bạn tưởng tượng rằng trò chơi Flash được viết bởi một lập trình viên mới, anh ta có thể lưu trữ các tham chiếu đến các yếu tố trò chơi trong nhiều cấu trúc dữ liệu, khiến chúng chia sẻ quyền sở hữu các tài nguyên trò chơi này. Thật không may, giả sử anh ta đã phạm sai lầm khi anh ta quên loại bỏ các yếu tố trò chơi khỏi một trong các cấu trúc dữ liệu khi chuyển sang giai đoạn tiếp theo, ngăn không cho chúng được giải phóng cho đến khi toàn bộ trò chơi bị tắt. Tuy nhiên, trò chơi vẫn hoạt động tốt vì các yếu tố không được rút ra hoặc ảnh hưởng đến tương tác của người dùng. Tuy nhiên, trò chơi bắt đầu sử dụng ngày càng nhiều bộ nhớ hơn trong khi tốc độ khung hình tự hoạt động với trình chiếu, trong khi quá trình xử lý ẩn vẫn lặp qua bộ sưu tập các yếu tố ẩn trong trò chơi này (hiện đã trở nên bùng nổ về kích thước). Đây là loại vấn đề tôi gặp phải thường xuyên trong các trò chơi Flash như vậy.

  • Tôi đã gặp những người nói rằng điều này không được tính là "rò rỉ bộ nhớ" bởi vì bộ nhớ vẫn đang được giải phóng khi đóng ứng dụng, và thay vào đó có thể được gọi là "rò rỉ không gian" hoặc một cái gì đó cho hiệu ứng này. Mặc dù sự phân biệt như vậy có thể hữu ích để xác định và nói về các vấn đề, tôi không thấy sự khác biệt đó rất hữu ích trong bối cảnh này nếu chúng ta nói về nó giống như nó không phải là vấn đề như "rò rỉ bộ nhớ" khi chúng ta giao dịch mục tiêu thực tế của việc đảm bảo phần mềm không làm tăng số lượng bộ nhớ vô lý khi chúng ta chạy nó lâu hơn (trừ khi chúng ta đang nói về các hệ điều hành tối nghĩa không giải phóng bộ nhớ của quá trình khi nó bị chấm dứt).

Bây giờ hãy nói rằng cùng một nhà phát triển vừa chớm nở đã viết trò chơi bằng C ++. Trong trường hợp đó, thông thường sẽ chỉ có một cấu trúc dữ liệu trung tâm trong trò chơi "sở hữu" bộ nhớ trong khi các cấu trúc khác chỉ vào bộ nhớ đó. Nếu anh ta mắc một lỗi tương tự, rất có thể, khi tiến tới giai đoạn tiếp theo, trò chơi sẽ bị sập do truy cập các con trỏ lơ lửng (hoặc tệ hơn, làm một việc khác ngoài sự cố).

Đây là loại đánh đổi ngay lập tức nhất mà tôi có xu hướng gặp phải trong miền của mình thường xuyên nhất giữa GC và không có GC. Và tôi thực sự không quan tâm đến GC rất nhiều trong lĩnh vực của mình, điều này không quá quan trọng, bởi vì những cuộc đấu tranh lớn nhất tôi từng gặp phải với phần mềm bị rò rỉ liên quan đến việc sử dụng GC trong một nhóm cũ gây ra rò rỉ được mô tả ở trên .

Trong miền cụ thể của tôi, tôi thực sự thích phần mềm bị sập hoặc bị trục trặc trong nhiều trường hợp bởi vì điều đó ít nhất dễ phát hiện hơn là cố gắng tìm ra lý do tại sao phần mềm tiêu thụ một cách bí ẩn lượng bộ nhớ sau khi chạy được nửa giờ trong khi tất cả chúng ta các bài kiểm tra đơn vị và tích hợp vượt qua mà không có khiếu nại (thậm chí từ Valgrind, vì bộ nhớ đang được giải phóng bởi GC khi tắt máy). Tuy nhiên, đó không phải là một cú đánh vào GC của tôi hay cố gắng nói rằng nó vô dụng hay bất cứ thứ gì tương tự, nhưng nó không phải là bất kỳ loại đạn bạc nào, thậm chí không gần gũi, trong các đội tôi làm việc chống lại phần mềm bị rò rỉ (để ngược lại tôi đã có trải nghiệm ngược lại với việc một codebase sử dụng GC là rò rỉ nhất tôi từng gặp). Công bằng mà nói, nhiều thành viên trong nhóm đó thậm chí còn không biết tài liệu tham khảo yếu là gì,

Sở hữu chung và Tâm lý học

Vấn đề tôi gặp phải với bộ sưu tập rác có thể khiến nó dễ bị "rò rỉ bộ nhớ" (và tôi sẽ khăng khăng gọi nó như là 'rò rỉ không gian' hoạt động theo cách chính xác theo quan điểm của người dùng) những người không sử dụng nó một cách cẩn thận liên quan đến "khuynh hướng của con người" ở một mức độ nào đó theo kinh nghiệm của tôi. Vấn đề với nhóm đó và cơ sở mã hóa rò rỉ nhất mà tôi từng gặp là họ dường như bị ấn tượng rằng GC sẽ cho phép họ ngừng suy nghĩ về việc ai sở hữu tài nguyên.

Trong trường hợp của chúng tôi, chúng tôi đã có rất nhiều đối tượng tham chiếu lẫn nhau. Các mô hình sẽ tham chiếu các tài liệu cùng với thư viện vật liệu và hệ thống đổ bóng. Tài liệu sẽ tham khảo kết cấu cùng với thư viện kết cấu và các shader nhất định. Máy ảnh sẽ lưu trữ các tham chiếu đến tất cả các loại thực thể cảnh cần được loại trừ khỏi kết xuất. Danh sách dường như tiếp tục vô tận. Điều đó đã tạo ra bất kỳ nguồn tài nguyên khổng lồ nào trong hệ thống được sở hữu và kéo dài suốt đời ở hơn 10 nơi khác trong trạng thái ứng dụng cùng một lúc, và điều đó rất, rất dễ bị lỗi của con người có thể chuyển thành rò rỉ (và không một vấn đề nhỏ, tôi đang nói về gigabyte trong vài phút với các vấn đề về khả năng sử dụng nghiêm trọng). Về mặt khái niệm, tất cả các tài nguyên này không cần phải chia sẻ quyền sở hữu, về mặt khái niệm chúng đều có một chủ sở hữu,

Nếu chúng ta ngừng suy nghĩ về việc ai sở hữu bộ nhớ nào, và vui vẻ chỉ lưu trữ các tài liệu tham khảo kéo dài suốt đời cho các đối tượng ở khắp mọi nơi mà không nghĩ về điều này, thì phần mềm sẽ không gặp sự cố do con trỏ lơ lửng nhưng gần như chắc chắn, theo như vậy suy nghĩ bất cẩn, bắt đầu rò rỉ bộ nhớ như điên theo những cách rất khó để theo dõi và sẽ trốn tránh các bài kiểm tra.

Nếu có một lợi ích thiết thực cho con trỏ lơ lửng trong miền của tôi, thì đó là nó gây ra sự cố và sự cố rất khó chịu. Và điều đó ít nhất có xu hướng khuyến khích các nhà phát triển, nếu họ muốn gửi một thứ gì đó đáng tin cậy, để bắt đầu suy nghĩ về quản lý tài nguyên và thực hiện những điều thích hợp cần thiết để loại bỏ tất cả các tham chiếu / con trỏ bổ sung cho một đối tượng không còn cần thiết về mặt khái niệm.

Quản lý tài nguyên ứng dụng

Quản lý tài nguyên phù hợp là tên của trò chơi nếu chúng ta đang nói về việc tránh rò rỉ trong các ứng dụng tồn tại lâu với trạng thái liên tục được lưu trữ trong đó việc rò rỉ sẽ gây ra các vấn đề nghiêm trọng về tốc độ khung hình và khả năng sử dụng. Và quản lý tài nguyên chính xác ở đây không kém phần khó khăn khi có hoặc không có GC. Công việc cũng không kém phần thủ công để loại bỏ các tham chiếu thích hợp đến các đối tượng không còn cần thiết cho dù chúng là con trỏ hay tham chiếu kéo dài suốt đời.

Đó là thách thức trong miền của tôi, không quên deletenhững gì chúng tôi new(trừ khi chúng tôi đang nói chuyện nghiệp dư với thử nghiệm, thực hành và công cụ kém chất lượng). Và nó đòi hỏi phải suy nghĩ và quan tâm xem chúng ta có sử dụng GC hay không.

Đa luồng

Một vấn đề khác tôi thấy rất hữu ích với GC, nếu nó có thể được sử dụng rất thận trọng trong miền của tôi, là để đơn giản hóa việc quản lý tài nguyên trong các bối cảnh đa luồng. Nếu chúng ta cẩn thận không lưu trữ các tham chiếu kéo dài suốt đời tới các tài nguyên ở nhiều nơi trong trạng thái ứng dụng, thì bản chất kéo dài trọn đời của các tham chiếu GC có thể cực kỳ hữu ích như một cách để các luồng tạm thời mở rộng tài nguyên được truy cập để mở rộng thời gian tồn tại của nó chỉ trong một khoảng thời gian ngắn khi cần thiết để xử lý xong.

Tôi nghĩ rằng việc sử dụng GC rất cẩn thận theo cách này có thể mang lại một phần mềm rất chính xác, không bị rò rỉ, đồng thời đơn giản hóa đa luồng.

Có nhiều cách xung quanh điều này mặc dù vắng mặt GC. Trong trường hợp của tôi, chúng tôi thống nhất biểu diễn thực thể cảnh của phần mềm, với các luồng tạm thời khiến tài nguyên cảnh được mở rộng trong thời gian ngắn theo cách khá khái quát trước giai đoạn dọn dẹp. Điều này có thể có mùi hơi giống với GC nhưng sự khác biệt là không có "quyền sở hữu chung" liên quan, chỉ có một thiết kế xử lý cảnh thống nhất trong các luồng xử lý phá hủy các tài nguyên nói trên. Tuy nhiên, sẽ đơn giản hơn nhiều nếu chỉ dựa vào GC ở đây nếu nó có thể được sử dụng rất cẩn thận với các nhà phát triển có lương tâm, cẩn thận sử dụng các tài liệu tham khảo yếu trong các lĩnh vực liên tục có liên quan, cho các trường hợp đa luồng như vậy.

C ++

Cuối cùng:

Trong C ++, tôi phải gọi xóa để loại bỏ một đối tượng được tạo vào cuối vòng đời của nó.

Trong Modern C ++, đây thường không phải là thứ bạn nên làm thủ công. Nó thậm chí không quá nhiều về việc quên làm điều đó. Khi bạn liên quan đến việc xử lý ngoại lệ vào hình ảnh, thì ngay cả khi bạn đã viết một deletecuộc gọi tương ứng bên dưới một số cuộc gọi đến new, một cái gì đó có thể ném vào giữa và không bao giờ thực hiện deletecuộc gọi nếu bạn không dựa vào các cuộc gọi hủy tự động được trình biên dịch chèn vào để thực hiện việc này bạn.

Với C ++, thực tế bạn cần phải làm như vậy, trừ khi bạn làm việc trong một bối cảnh được nhúng với các ngoại lệ và các thư viện đặc biệt được lập trình một cách có chủ ý không ném, tránh việc dọn dẹp tài nguyên thủ công như vậy (bao gồm tránh các cuộc gọi thủ công để mở khóa mutex bên ngoài dtor , ví dụ, và không chỉ giải quyết bộ nhớ). Xử lý ngoại lệ đòi hỏi khá nhiều, vì vậy hầu hết việc dọn dẹp tài nguyên nên được tự động hóa thông qua các bộ hủy.

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.