Tại sao bộ sưu tập rác chỉ mở rộng đến bộ nhớ mà không phải các loại tài nguyên khác?


12

Có vẻ như mọi người đã mệt mỏi với việc quản lý bộ nhớ thủ công, vì vậy họ đã phát minh ra bộ sưu tập rác và cuộc sống khá tốt. Nhưng những gì về tất cả các loại tài nguyên khác? Mô tả tập tin, ổ cắm, hoặc thậm chí người dùng tạo dữ liệu như kết nối cơ sở dữ liệu?

Điều này cảm thấy giống như một câu hỏi ngây thơ nhưng tôi không thể tìm thấy bất cứ nơi nào mà bất cứ ai đã hỏi nó. Hãy xem xét mô tả tập tin. Nói rằng một chương trình biết rằng nó sẽ chỉ được phép có sẵn 4000 fds khi nó bắt đầu. Bất cứ khi nào nó thực hiện một hoạt động sẽ mở một mô tả tập tin, nếu nó sẽ

  1. Kiểm tra để đảm bảo rằng nó sẽ không hết.
  2. Nếu có, kích hoạt trình thu gom rác, sẽ giải phóng một loạt bộ nhớ.
  3. Nếu một số bộ nhớ được giải phóng giữ các tham chiếu đến bộ mô tả tệp, hãy đóng chúng ngay lập tức. Nó biết bộ nhớ thuộc về một tài nguyên vì bộ nhớ gắn với tài nguyên đó đã được đăng ký vào 'sổ đăng ký mô tả tệp', vì thiếu một thuật ngữ tốt hơn, khi nó được mở lần đầu tiên.
  4. Mở một bộ mô tả tệp mới, sao chép nó vào bộ nhớ mới, đăng ký vị trí bộ nhớ đó vào 'sổ đăng ký mô tả tệp' và trả lại cho người dùng.

Vì vậy, tài nguyên sẽ không được giải phóng kịp thời, nhưng nó sẽ được giải phóng bất cứ khi nào gc chạy bao gồm ít nhất, ngay trước khi tài nguyên sắp hết, giả sử nó không được sử dụng hoàn toàn.

Và có vẻ như điều đó là đủ cho nhiều vấn đề dọn dẹp tài nguyên do người dùng định nghĩa. Tôi đã tìm thấy một nhận xét duy nhất ở đây rằng các tham chiếu thực hiện dọn dẹp tương tự như trong C ++ với một luồng chứa tham chiếu đến tài nguyên và làm sạch nó khi chỉ còn một tham chiếu duy nhất (từ luồng dọn dẹp), nhưng tôi có thể ' Không tìm thấy bất kỳ bằng chứng nào về việc đây là một thư viện hoặc một phần của bất kỳ ngôn ngữ hiện có nào.

Câu trả lời:


4

GC liên quan đến một nguồn tài nguyên dự đoán và dành riêng . VM có toàn quyền kiểm soát nó và có toàn quyền kiểm soát những trường hợp nào được tạo và khi nào. Các từ khóa ở đây là "dành riêng" và "tổng kiểm soát". Xử lý được phân bổ bởi HĐH và con trỏ là ... con trỏ tốt cho các tài nguyên được phân bổ bên ngoài không gian được quản lý. Do đó, các thẻ điều khiển và con trỏ không bị hạn chế sử dụng bên trong mã được quản lý. Chúng có thể được sử dụng - và thường là - bởi mã được quản lý và không được quản lý chạy trên cùng một quy trình.

Một "Trình thu thập tài nguyên" sẽ có thể xác minh xem một tay cầm / con trỏ có được sử dụng trong một không gian được quản lý hay không, nhưng theo định nghĩa thì nó không biết về những gì xảy ra bên ngoài không gian bộ nhớ (và, để làm cho mọi thứ tồi tệ hơn có thể được sử dụng qua ranh giới quá trình).

Một ví dụ thực tế là .NET CLR. Người ta có thể sử dụng C ++ có hương vị để viết mã hoạt động với cả không gian bộ nhớ được quản lý và không được quản lý; xử lý, con trỏ và tham chiếu có thể được chuyển qua giữa mã được quản lý và không được quản lý. Mã không được quản lý phải sử dụng các cấu trúc / loại đặc biệt để cho phép CLR tiếp tục theo dõi các tham chiếu được tạo cho các tài nguyên được quản lý của nó. Nhưng đó là điều tốt nhất nó có thể làm. Nó không thể làm tương tự với các thẻ điều khiển và con trỏ, và do đó, Bộ sưu tập tài nguyên đã nói sẽ không biết liệu có ổn không khi phát hành một tay cầm hoặc con trỏ cụ thể.

chỉnh sửa: Liên quan đến .NET CLR, tôi không có kinh nghiệm phát triển C ++ với nền tảng .NET. Có thể có các cơ chế đặc biệt tại chỗ cho phép CLR tiếp tục theo dõi các tham chiếu đến các thẻ điều khiển / con trỏ giữa mã được quản lý và không được quản lý. Nếu đó là trường hợp thì CLR có thể xử lý thời gian tồn tại của các tài nguyên đó và giải phóng chúng khi có tất cả các tham chiếu đến chúng (ít nhất là trong một số trường hợp có thể). Dù bằng cách nào, các thực tiễn tốt nhất chỉ ra rằng xử lý (đặc biệt là những người trỏ đến tệp) và con trỏ nên được phát hành ngay khi không cần thiết. Bộ sưu tập tài nguyên sẽ không tuân thủ điều đó, đó là một lý do khác để không có.

chỉnh sửa 2: Nói chung, CLR / JVM / VM nói chung để viết một số mã để giải phóng một tay cầm cụ thể nếu nó chỉ được sử dụng trong không gian được quản lý. Trong .NET sẽ là một cái gì đó như:

// This class offends many best practices, but it would do the job.
public class AutoReleaseFileHandle {
    // keeps track of how many instances of this class is in memory
    private static int _toBeReleased = 0;

    // the threshold when a garbage collection should be forced
    private const int MAX_FILES = 100;

    public AutoReleaseFileHandle(FileStream fileStream) {
       // Force garbage collection if max files are reached.
       if (_toBeReleased >= MAX_FILES) {
          GC.Collect();
       }
       // increment counter
       Interlocked.Increment(ref _toBeReleased);
       FileStream = fileStream;
    }

    public FileStream { get; private set; }

    private void ReleaseFileStream(FileStream fs) {
       // decrement counter
       Interlocked.Decrement(ref _toBeReleased);
       FileStream.Close();
       FileStream.Dispose();
       FileStream = null;
    }

    // Close and Dispose the Stream when this class is collected by the GC.
    ~AutoReleaseFileHandle() {
       ReleaseFileStream(FileStream);
    }

    // because it's .NET this class should also implement IDisposable
    // to allow the user to dispose the resources imperatively if s/he wants 
    // to.
    private bool _disposed = false;
    public void Dispose() {
      if (_disposed) {
        return;
      }
      _disposed = true;
      // tells GC to not call the finalizer for this instance.
      GC.SupressFinalizer(this);

      ReleaseFileStream(FileStream);
    }
}

// use it
// for it to work, fs.Dispose() should not be called directly,
var fs = File.Open("path/to/file"); 
var autoRelease = new AutoReleaseFileHandle(fs);

3

Đây dường như là một trong những lý do ngôn ngữ với người thu gom rác thực hiện các công cụ hoàn thiện. Công cụ hoàn thiện nhằm mục đích cho phép lập trình viên dọn sạch tài nguyên của đối tượng trong quá trình thu gom rác. Vấn đề lớn với người hoàn thiện là họ không được đảm bảo để chạy.

Có một bài viết khá hay về việc sử dụng các công cụ hoàn thiện ở đây:

Hoàn thiện đối tượng và dọn dẹp

Trong thực tế, nó đặc biệt sử dụng bộ mô tả tập tin làm ví dụ. Bạn nên đảm bảo tự dọn sạch tài nguyên đó, nhưng có một cơ chế có thể khôi phục tài nguyên chưa được phát hành đúng cách.


Tôi không chắc chắn nếu điều này trả lời câu hỏi của tôi. Nó thiếu một phần trong đề xuất của tôi, nơi hệ thống biết rằng nó sắp hết tài nguyên. Cách duy nhất để bổ sung phần đó là đảm bảo rằng bạn chạy gc theo cách thủ công trước khi phân bổ bộ mô tả tệp mới, nhưng điều đó cực kỳ không hiệu quả và tôi không biết liệu bạn có thể khiến gc chạy trong java không.
đọc ý tưởng

OK, nhưng bộ mô tả tệp thường đại diện cho một tệp mở trong hệ điều hành ngụ ý (tùy thuộc vào hệ điều hành) sử dụng các tài nguyên ở cấp hệ thống như khóa, vùng đệm, vùng cấu trúc, v.v. Thành thật mà nói, tôi không thấy lợi ích của việc để các cấu trúc này mở cho một bộ sưu tập rác sau này và tôi thấy nhiều bất lợi để chúng được phân bổ lâu hơn cần thiết. Các phương thức Finalize () được dự định để cho phép dọn sạch mương cuối cùng trong trường hợp lập trình viên bỏ qua các cuộc gọi để dọn sạch tài nguyên, nhưng không nên dựa vào.
Brian Hibbert

Tôi hiểu rằng lý do họ không nên dựa vào là nếu bạn phân bổ một tấn các tài nguyên này, giống như có thể bạn đang hạ xuống một hệ thống phân cấp tệp mở mỗi tệp, bạn có thể mở quá nhiều tệp trước khi gc xảy ra chạy, gây ra một vụ nổ. Điều tương tự sẽ xảy ra với bộ nhớ, ngoại trừ việc bộ kiểm tra thời gian chạy để đảm bảo nó sẽ không hết bộ nhớ. Tôi muốn biết lý do tại sao một hệ thống không thể được thực hiện để lấy lại các tài nguyên tùy ý trước khi nổ tung, gần như chính xác như cách thực hiện bộ nhớ.
đọc ý tưởng

Một COULD hệ thống được ghi vào tài nguyên GC ngoài bộ nhớ, nhưng bạn sẽ phải theo dõi số tham chiếu hoặc có một số phương pháp xác định khác khi tài nguyên không còn được sử dụng. Bạn không muốn phân bổ và phân bổ lại các tài nguyên vẫn đang được sử dụng. Tất cả các trang của tình trạng lộn xộn có thể xảy ra nếu một luồng có một tệp được mở để ghi, HĐH "thu hồi" xử lý tệp và một luồng khác mở một tệp khác để ghi bằng cách sử dụng cùng một xử lý. Và tôi cũng vẫn đề nghị rằng sẽ lãng phí tài nguyên đáng kể để mở chúng cho đến khi một chủ đề giống như GC xuất hiện để phát hành chúng.
Brian Hibbert

3

Có nhiều kỹ thuật lập trình để giúp quản lý các loại tài nguyên này.

  • Các lập trình viên C ++ thường sử dụng một mẫu gọi là Resource Acquisition là Khởi tạo hoặc viết tắt là RAII. Mẫu này đảm bảo rằng khi một đối tượng giữ tài nguyên nằm ngoài phạm vi, nó sẽ đóng các tài nguyên mà nó đang giữ. Điều này hữu ích khi thời gian tồn tại của đối tượng tương ứng với một phạm vi cụ thể trong chương trình (ví dụ: khi nó khớp với thời gian khi một khung ngăn xếp cụ thể xuất hiện trên ngăn xếp), vì vậy nó rất hữu ích cho các đối tượng được trỏ bởi các biến cục bộ (con trỏ các biến được lưu trữ trên ngăn xếp), nhưng không hữu ích cho các đối tượng được trỏ đến bởi các con trỏ được lưu trữ trên heap.

  • Java, C # và nhiều ngôn ngữ khác cung cấp một cách để chỉ định một phương thức sẽ được gọi khi một đối tượng không còn tồn tại và sắp được thu thập bởi trình thu gom rác. Xem, ví dụ, quyết toán dispose(), và những người khác. Ý tưởng là lập trình viên có thể thực hiện một phương thức như vậy để nó sẽ đóng tài nguyên một cách rõ ràng trước khi đối tượng được giải phóng bởi trình thu gom rác. Tuy nhiên, những cách tiếp cận này có một số vấn đề, mà bạn có thể đọc về nơi khác; chẳng hạn, trình thu gom rác có thể không thu thập đối tượng cho đến khi muộn hơn bạn muốn.

  • C # và các ngôn ngữ khác cung cấp một usingtừ khóa giúp đảm bảo rằng các tài nguyên được đóng sau khi chúng không còn cần thiết (vì vậy bạn đừng quên đóng bộ mô tả tệp hoặc tài nguyên khác). Điều này thường tốt hơn là dựa vào trình thu gom rác để phát hiện ra rằng đối tượng không còn tồn tại. Xem, ví dụ: /programming//q/75401/781723 . Thuật ngữ chung ở đây là một tài nguyên được quản lý . Khái niệm này được xây dựng trên RAII và hoàn thiện, cải thiện chúng theo một số cách.


Tôi ít quan tâm đến việc giải quyết tài nguyên kịp thời và quan tâm nhiều hơn đến ý tưởng về việc giải quyết đúng lúc. RIAA là tuyệt vời, nhưng không thể áp dụng cho rất nhiều ngôn ngữ thu gom rác. Java đang thiếu khả năng biết khi nào nó sắp hết một tài nguyên nhất định. Các hoạt động sử dụng và loại khung là hữu ích và xử lý lỗi, nhưng tôi không quan tâm đến chúng. Tôi chỉ đơn giản muốn phân bổ tài nguyên và sau đó họ sẽ tự dọn dẹp bất cứ khi nào thuận tiện hoặc cần thiết, và có rất ít cách để làm hỏng nó. Tôi đoán không ai đã thực sự nhìn vào điều này.
mindreader 6/2/2016

2

Tất cả bộ nhớ đều bằng nhau, nếu tôi yêu cầu 1K, tôi không quan tâm đến không gian địa chỉ trong 1K.

Khi tôi yêu cầu xử lý tệp, tôi muốn xử lý tệp mà tôi muốn mở. Có một tệp xử lý mở trên một tệp, thường chặn truy cập vào tệp bởi các quy trình hoặc máy khác.

Do đó, các tệp xử lý tệp phải được đóng ngay khi không cần thiết, nếu không chúng sẽ chặn các quyền truy cập khác vào tệp, nhưng bộ nhớ chỉ cần được thu hồi khi bạn bắt đầu dùng hết.

Việc chạy pass GC rất tốn kém và chỉ được thực hiện khi cần thiết, không thể dự đoán khi nào quá trình bao phấn sẽ cần một tệp xử lý mà quy trình của bạn có thể không còn sử dụng nữa, nhưng vẫn mở.


Câu trả lời của bạn đạt được chìa khóa thực sự: bộ nhớ có thể bị nhiễm nấm và hầu hết các hệ thống đều có đủ để không cần phải thu hồi một cách nhanh chóng. Ngược lại, nếu một chương trình có quyền truy cập độc quyền vào một tệp, điều đó sẽ chặn bất kỳ chương trình nào khác ở mọi nơi trong vũ trụ có thể cần sử dụng tệp đó, bất kể có bao nhiêu tệp khác có thể tồn tại.
supercat

0

Tôi sẽ đoán lý do tại sao điều này không được tiếp cận nhiều đối với các tài nguyên khác là chính xác bởi vì hầu hết các tài nguyên khác được ưu tiên phát hành càng sớm càng tốt cho bất kỳ ai sử dụng lại.

Tất nhiên, lưu ý, ví dụ của bạn có thể được cung cấp bằng cách sử dụng các mô tả tệp "yếu" với các kỹ thuật GC hiện có.


0

Để kiểm tra xem bộ nhớ không còn truy cập được nữa (và do đó đảm bảo không sử dụng nữa) khá dễ dàng. Hầu hết các loại tài nguyên khác có thể được xử lý bằng các kỹ thuật tương tự hoặc ít hơn (nghĩa là thu nhận tài nguyên là khởi tạo, RAII và đối tác giải phóng khi người dùng bị hủy, liên kết nó với quản trị bộ nhớ). Nói chung, thực hiện một số loại giải phóng "đúng lúc" là không thể (kiểm tra vấn đề tạm dừng, bạn sẽ phải tìm ra rằng một số tài nguyên đã được sử dụng lần cuối cùng). Vâng, đôi khi nó có thể được thực hiện tự động, nhưng nó là một trường hợp lộn xộn hơn nhiều như bộ nhớ. Vì vậy, nó phụ thuộc vào sự can thiệp của người dùng trong hầu hết các phần.

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.