2010-08-24 7 views
8

Giám sát việc sử dụng Byte ảo của chương trình trong khi nó đang chạy cho thấy rằng bằng cách thực hiện một số thao tác loại, việc sử dụng byte ảo tăng khoảng 1GB trong khoảng 5 phút. Chương trình giao dịch với ổ cắm tcp và thông lượng truyền dữ liệu cao giữa chúng (~ 800Mbps).windbg "miễn phí" loại đối tượng

Tải tệp kết xuất của chương trình trong windbg cho thấy lý do sử dụng bộ nhớ rất cao và nhanh là khoảng 1GB đối tượng "miễn phí". Thật vậy, khi tôi gọi bộ thu gom rác (gen 0, 1 & 2) từ màn hình bảng điều khiển của chương trình (sau khi nhận được nó đến trạng thái này) nó giải phóng khoảng 1GB sử dụng bộ nhớ.

Tôi đang cố gắng hiểu chính xác những đối tượng miễn phí này là gì và tại sao chúng không phải là rác do bộ thu gom rác thu thập tự động.

Chỉnh sửa: Một gợi ý là tôi có thể tạo các đối tượng trong Heap đối tượng lớn và nó trở thành không mong muốn nhưng đây không phải là trường hợp như tôi đã thấy tất cả các đối tượng "miễn phí" nằm trong Gen 2 Heap.

Đề nghị khác là có thể Gen 2 Heap bị phân mảnh vì đối tượng được ghim nhưng nếu đúng như vậy, GC.Collect sẽ không khắc phục được sự cố nhưng nó thực sự làm như vậy tôi tin rằng đây không phải là trường hợp.

Điều tôi nghi ngờ từ cuộc thảo luận với Paul là bộ nhớ được giải phóng nhưng là từ một lý do nào đó quay trở lại hệ điều hành hiếm khi hoặc chỉ khi tôi gọi GC.Collect theo cách thủ công.

Trả lời

7

Chúng không phải là 'đối tượng' miễn phí, chúng là không gian trống. .NET không phát hành bộ nhớ mà nó đã sử dụng trở lại hệ điều hành ngay lập tức. Bất kỳ khối miễn phí nào cũng có thể được sử dụng cho phân bổ đối tượng tiếp theo, cung cấp chúng phù hợp bên trong khối tự do (nếu không thì phần mở rộng phải được mở rộng bằng cách yêu cầu hệ điều hành cấp phát bộ nhớ nhiều hơn).

Bộ thu gom rác cố gắng kết hợp không gian trống vào khối có thể sử dụng lớn bằng cách nén thế hệ 2. Điều này không phải lúc nào cũng có thể: ví dụ: ứng dụng có thể ghim các đối tượng có khả năng ngăn chặn bộ thu gom rác kết hợp không gian trống đối tượng phía trước heap. Nếu điều này xảy ra rất nhiều, bộ nhớ của ứng dụng sẽ được chia thành các khối nhỏ vô dụng và ảnh hưởng này được gọi là 'phân mảnh đống'.

Ngoài ra, có một đối tượng lớn (LOH) trong đó các đối tượng lớn hơn được phân bổ. Lý do là có một chi phí liên quan đến đống nén khi dữ liệu phải được sao chép xung quanh và vì vậy LOH không được nén chặt, tránh các chi phí này. Tuy nhiên, flipside là LOH có thể dễ dàng bị phân mảnh, với các khối nhỏ, ít hữu ích của bộ nhớ miễn phí xen kẽ giữa các đối tượng trực tiếp.

Tôi khuyên bạn nên chạy dumpheap -stat. Lệnh này sẽ báo cáo ở cuối danh sách bất kỳ khối phân mảnh nào. Sau đó bạn có thể đổ những khối đó để có ý tưởng về những gì đang xảy ra.

+0

Cảm ơn câu trả lời. Kiểm tra kích thước heaps cho thấy rằng Gen 2 là một trong những chiếm tất cả bộ nhớ và thực sự gọi GC.Collect (0) hoặc GC.Collect (1) đã không giải phóng bộ nhớ trong khi GC.Collect (2) đã thực hiện việc làm. # đối tượng được ghim vẫn ở mức thấp trong suốt quá trình thực hiện. Khi tôi theo dõi bộ sưu tập # Gen 2 trong perfmon tôi thấy rằng nó tăng liên tục nhưng bộ nhớ từ Gen2 rất hiếm khi được giải phóng tự động. Nó được giải phóng khi tôi gọi GC.Collect (2) mặc dù, như đã đề cập ở trên. Điều gì có thể exaplin này? – galbarm

+0

@galbarm: Có vẻ như đó chỉ là chiến lược mà CLR .NET thực hiện để quản lý bộ nhớ. Hãy nhớ rằng bộ nhớ .NET sử dụng cho các heap được quản lý phải được phân bổ bởi hệ điều hành và hoạt động này rõ ràng là có phí. Nó sẽ có ý nghĩa cho nó để treo vào bộ nhớ hơn là liên tục cấp phát và phát hành khối bộ nhớ. Như một thử nghiệm, bạn có thể thử tạo ra một quá trình thứ hai mà gobbles bộ nhớ và xem nếu một sự kiện bộ nhớ hệ thống thấp kích hoạt ứng dụng hiện tại của bạn vào phát hành bộ nhớ miễn phí nó có sẵn. –

+0

Làm thế nào tôi có thể thấy sự gia tăng liên tục trong bộ sưu tập # Gen 2 nhưng bộ nhớ không được giải phóng cho đến khi tôi sử dụng thủ công GC.Collect? – galbarm

5

Bằng cách này, có vẻ như bạn có một vấn đề nổi tiếng (ít nhất là trong số rất nhiều ổ cắm) mà hầu hết các máy chủ socket đều có trong .Net. Paul đã chạm vào ý nghĩa của nó. Để xây dựng thêm, những gì đang xảy ra là trong khi đọc/ghi trên ổ cắm, bộ đệm sẽ được ghim - điều đó có nghĩa là GC không được phép di chuyển nó xung quanh (như những mảnh heap của bạn). Coverant (người tiên phong trong giải pháp) đã nhìn thấy một OutOfMemoryException khi sử dụng bộ nhớ thực tế của họ chỉ khoảng 500MB (do sự phân mảnh nặng như vậy). Sửa chữa nó là một câu chuyện hoàn toàn khác.

Những gì bạn muốn làm là lúc khởi động ứng dụng phân bổ một số lượng rất lớn bộ đệm (Tôi hiện đang làm 50MB).Bạn sẽ tìm thấy các lớp học ArraySegment<T> (v2.0) và ConcurrentQueue<T> (v4.0) mới đặc biệt hữu ích khi viết bài này. Có một vài hàng đợi không có khóa nổi trên các ống nếu bạn chưa sử dụng v4.0.

// Pseudo-code. 
ArraySegment<byte> CheckOut() 
{ 
    ArraySegment<byte> result; 
    while(!_queue.TryDequeue(out result)) 
    GrowBufferQueue(); //Enqueue a bunch more buffers. 
    return result; 
} 

void CheckOut(ArraySegment<byte> buffer) 
{ 
    _queue.Enqueue(buffer); 
} 

void GrowBufferQueue() 
{ 
    // Verify this, I did throw it together in 30s. 
    // Allocates nearly 2MB. You might want to tweak that. 
    for(var j = 0; j < 5; j++) 
    { 
    var buffer = new byte[409600]; // 4096 = page size on Windows. 
    for(var i = 0; i < 409600; i += 4096) 
     _queue.Enqueue(new ArraySegment<byte>(buffer, i, 4096)); 
    } 
} 

Sau đó, bạn sẽ cần phải phân lớp đệm với một từ vùng đệm của bạn. Buffer::BlockCopy sẽ giúp hiệu suất (không sử dụng Array::Copy). Điều này là phức tạp và lông; đặc biệt là nếu bạn làm cho nó có khả năng không đồng bộ.

suối Nếu bạn không layering (ví dụ SSLStream < ->DeflateStream < ->XmlWriter vv), bạn nên sử dụng new socket async pattern in .Net 4.0; trong đó có hiệu quả hơn xung quanh các IAsyncResult s mà có được thông qua xung quanh. Vì bạn không sắp xếp các luồng, bạn có toàn quyền kiểm soát các bộ đệm được sử dụng - vì vậy bạn không cần phải đi theo tuyến đường con của NetworkStream.