2013-02-06 27 views
9

Hãy xem xét các đoạn mã sau:Tại sao bộ thu gom rác C# không tiếp tục cố gắng giải phóng bộ nhớ cho đến khi yêu cầu có thể được thỏa mãn?

using System; 

namespace memoryEater 
{ 
    internal class Program 
    { 
     private static void Main(string[] args) 
     { 
      Console.WriteLine("alloc 1"); 
      var big1 = new BigObject(); 

      Console.WriteLine("alloc 2"); 
      var big2 = new BigObject(); 

      Console.WriteLine("null 1"); 
      big1 = null; 

      //GC.Collect(); 

      Console.WriteLine("alloc3"); 
      big1 = new BigObject(); 

      Console.WriteLine("done"); 
      Console.Read(); 
     } 
    } 

    public class BigObject 
    { 
     private const uint OneMeg = 1024 * 1024; 
     private static int _idCnt; 
     private readonly int _myId; 
     private byte[][] _bigArray; 

     public BigObject() 
     { 
      _myId = _idCnt++; 
      Console.WriteLine("BigObject {0} creating... ", _myId); 

      _bigArray = new byte[700][]; 

      for (int i = 0; i < 700; i++) 
      { 
       _bigArray[i] = new byte[OneMeg]; 
      } 

      for (int j = 0; j < 700; j++) 
      { 
       for (int i = 0; i < OneMeg; i++) 
       { 
        _bigArray[j][i] = (byte)i; 
       } 
      } 
      Console.WriteLine("done"); 
     } 

     ~BigObject() 
     { 
      Console.WriteLine("BigObject {0} finalised", _myId); 
     } 
    } 
} 

Tôi có một lớp học, BigObject, mà tạo ra một mảng 700MiB trong constructor của nó, và có một phương thức Finalize mà không làm gì khác hơn là in ra màn hình. Trong Main, tôi tạo ra hai đối tượng này, một đối tượng miễn phí và sau đó tạo một đối tượng thứ ba.

Nếu điều này được biên dịch cho 32 bit (để giới hạn bộ nhớ thành 2 hợp đồng biểu diễn), ngoại lệ hết bộ nhớ được ném khi tạo BigObject thứ ba. Điều này là do, khi bộ nhớ được yêu cầu lần thứ ba, yêu cầu không thể được đáp ứng và do đó bộ thu gom rác sẽ chạy. Tuy nhiên, BigObject đầu tiên, đã sẵn sàng để được thu thập, có một phương pháp finaliser vì vậy thay vì được thu thập được đặt trên hàng đợi cuối cùng và được hoàn thành. Người thu gom rác sau đó dừng lại và ngoại lệ được ném ra. Tuy nhiên, nếu cuộc gọi đến GC.Collect là không được chú ý, hoặc phương thức hoàn thành được loại bỏ, mã sẽ chạy tốt.

Câu hỏi của tôi là, tại sao bộ thu gom rác không thực hiện mọi thứ có thể để đáp ứng yêu cầu bộ nhớ? Nếu nó chạy hai lần (một lần để hoàn thành và một lần nữa để miễn phí) mã trên sẽ làm việc tốt. Không nên bộ gom rác tiếp tục hoàn thành và thu thập cho đến khi không còn bộ nhớ nào có thể được giải phóng trước khi ném ngoại lệ, và có cách nào để cấu hình nó hoạt động theo cách này (hoặc bằng mã hoặc thông qua Visual Studio) không?

+1

Vấn đề là do tác dụng phụ trong finalizer. Đừng làm thế! – leppie

+2

Tôi đã xem qua một vài năm trở lại và viết một bài đăng trên blog. Xem: http://xacc.wordpress.com/2011/02/22/gc-suppressfinalize/ (xem chú thích) – leppie

+0

Thú vị, nhưng mã vẫn thất bại nếu Console.WriteLine được lấy ra khỏi finaliser. –

Trả lời

0

Tôi đoán là vì thời gian trình hoàn thành thực hiện trong quá trình thu thập rác là không xác định. Tài nguyên không được đảm bảo sẽ được phát hành tại bất kỳ thời điểm cụ thể nào (trừ khi gọi phương thức Close hoặc phương thức Dispose.), Cũng là thứ tự mà finalizers được chạy ngẫu nhiên để bạn có thể có finalizer trên một đối tượng khác đang đợi, trong khi đối tượng của bạn chờ đợi .

2

Tính không xác định của nó khi GC sẽ hoạt động và cố gắng lấy lại bộ nhớ.

Nếu bạn thêm dòng này sau big1 = null. Tuy nhiên bạn nên cẩn thận về việc buộc GC phải thu thập. Nó không được khuyến khích trừ khi bạn biết những gì bạn đang làm.

GC.Collect(); 
GC.WaitForPendingFinalizers(); 

Best Practice for Forcing Garbage Collection in C#

When should I use GC.SuppressFinalize()?

Garbage collection in .NET (generations)

+0

Tôi hiểu rằng đây là thực tiễn không tốt, nó chỉ là một ứng dụng demo để chứng minh vấn đề. Câu hỏi đặt ra là, tại sao bộ thu gom rác không chạy các finalisers và bộ nhớ miễn phí (cho phép phân bổ được thực hiện) trước khi đưa ra ngoại lệ? –

+0

@SeanReid http://stackoverflow.com/questions/10016541/garbage-collection-not-happening-even-when-needed this. Nó bây giờ sẽ được rõ ràng lý do tại sao các GC không bao giờ xảy ra tự động. Bạn chỉ đang tạo các đối tượng trên LOH (hãy nhớ rằng các kiểu int, cách bạn đã sử dụng chúng, được cấp phát trên stack và không cần phải thu thập). Bạn không bao giờ lấp đầy thế hệ 0, do đó GC không bao giờ xảy ra. – adt

+0

Vấn đề là, GC chạy, và finaliser của đối tượng free'd cũng chạy. (Lưu ý ứng dụng là 32 bit, để đảm bảo chúng tôi hết bộ nhớ ở 2Gb thay vì bắt đầu trang cho bộ nhớ). Vấn đề là GC cần phải chạy hai lần, một lần để chạy finaliser và một lần nữa để thực sự giải phóng bộ nhớ. –

Các vấn đề liên quan