Ứng dụng C # /. NET mà tôi đang làm việc bị rò rỉ bộ nhớ chậm. Tôi đã sử dụng CDB với SOS để cố gắng xác định điều gì đang xảy ra nhưng dữ liệu dường như không có ý nghĩa gì vì vậy tôi hy vọng một trong số các bạn có thể đã trải qua điều này trước đây.
Ứng dụng đang chạy trên khuôn khổ 64 bit. Nó liên tục tính toán và tuần tự hóa dữ liệu tới một máy chủ từ xa và đang tấn công Khối Đối tượng Lớn (LOH) một chút. Tuy nhiên, hầu hết các đối tượng LOH mà tôi mong đợi là tạm thời: sau khi tính toán hoàn tất và đã được gửi đến máy chủ từ xa, bộ nhớ sẽ được giải phóng. Tuy nhiên, những gì tôi đang thấy là một số lượng lớn các mảng đối tượng (trực tiếp) xen kẽ với các khối bộ nhớ miễn phí, ví dụ: lấy một đoạn ngẫu nhiên từ LOH:
0:000> !DumpHeap 000000005b5b1000 000000006351da10
Address MT Size
...
000000005d4f92e0 0000064280c7c970 16147872
000000005e45f880 00000000001661d0 1901752 Free
000000005e62fd38 00000642788d8ba8 1056 <--
000000005e630158 00000000001661d0 5988848 Free
000000005ebe6348 00000642788d8ba8 1056
000000005ebe6768 00000000001661d0 6481336 Free
000000005f214d20 00000642788d8ba8 1056
000000005f215140 00000000001661d0 7346016 Free
000000005f9168a0 00000642788d8ba8 1056
000000005f916cc0 00000000001661d0 7611648 Free
00000000600591c0 00000642788d8ba8 1056
00000000600595e0 00000000001661d0 264808 Free
...
Rõ ràng là tôi sẽ mong đợi điều này xảy ra nếu ứng dụng của tôi đang tạo ra các đối tượng lớn, tồn tại lâu dài trong mỗi lần tính toán. (Nó làm được điều này và tôi chấp nhận rằng sẽ có một mức độ phân mảnh LOH nhưng đó không phải là vấn đề ở đây.) Vấn đề là mảng đối tượng rất nhỏ (1056 byte) bạn có thể thấy trong kết xuất ở trên mà tôi không thể thấy trong mã đang được tạo và vẫn được root bằng cách nào đó.
Cũng lưu ý rằng CDB không báo cáo loại khi phân đoạn heap bị kết xuất: Tôi không chắc liệu điều này có liên quan hay không. Nếu tôi kết xuất đối tượng được đánh dấu (<-), CDB / SOS báo cáo nó tốt:
0:015> !DumpObj 000000005e62fd38
Name: System.Object[]
MethodTable: 00000642788d8ba8
EEClass: 00000642789d7660
Size: 1056(0x420) bytes
Array: Rank 1, Number of elements 128, Type CLASS
Element Type: System.Object
Fields:
None
Các phần tử của mảng đối tượng là tất cả các chuỗi và các chuỗi có thể nhận dạng được từ mã ứng dụng của chúng tôi.
Ngoài ra, tôi không thể tìm thấy gốc GC của chúng vì lệnh! GCRoot bị treo và không bao giờ quay trở lại (tôi thậm chí đã thử để nó qua đêm).
Vì vậy, tôi sẽ đánh giá rất cao nếu ai đó có thể làm sáng tỏ tại sao các mảng đối tượng nhỏ (<85k) này lại kết thúc trên LOH: .NET sẽ đưa một mảng đối tượng nhỏ vào đó trong những tình huống nào? Ngoài ra, có ai tình cờ biết về một cách khác để xác định nguồn gốc của những vật thể này không?
Cập nhật 1
Một giả thuyết khác mà tôi đưa ra vào cuối ngày hôm qua là các mảng đối tượng này bắt đầu lớn nhưng đã bị thu hẹp lại để lại các khối bộ nhớ trống hiện rõ trong các bãi chứa bộ nhớ. Điều khiến tôi nghi ngờ là các mảng đối tượng luôn có vẻ dài 1056 byte (128 phần tử), 128 * 8 cho các tham chiếu và 32 byte cho chi phí.
Ý tưởng là có lẽ một số mã không an toàn trong thư viện hoặc trong CLR đang làm hỏng trường số phần tử trong tiêu đề mảng. Hơi dài mà tôi biết ...
Cập nhật 2
Nhờ Brian Rasmussen (xem câu trả lời được chấp nhận), vấn đề đã được xác định là sự phân mảnh của LOH gây ra bởi bảng thực tập chuỗi! Tôi đã viết một ứng dụng thử nghiệm nhanh để xác nhận điều này:
static void Main()
{
const int ITERATIONS = 100000;
for (int index = 0; index < ITERATIONS; ++index)
{
string str = "NonInterned" + index;
Console.Out.WriteLine(str);
}
Console.Out.WriteLine("Continue.");
Console.In.ReadLine();
for (int index = 0; index < ITERATIONS; ++index)
{
string str = string.Intern("Interned" + index);
Console.Out.WriteLine(str);
}
Console.Out.WriteLine("Continue?");
Console.In.ReadLine();
}
Đầu tiên, ứng dụng tạo và bỏ tham chiếu các chuỗi duy nhất trong một vòng lặp. Điều này chỉ để chứng minh rằng bộ nhớ không bị rò rỉ trong trường hợp này. Rõ ràng là nó không nên và nó không.
Trong vòng lặp thứ hai, các chuỗi duy nhất được tạo và thực hiện. Hành động này bắt nguồn từ bảng thực tập. Điều tôi không nhận ra là bảng thực tập được thể hiện như thế nào. Có vẻ như nó bao gồm một tập hợp các trang - mảng đối tượng gồm 128 phần tử chuỗi - được tạo trong LOH. Điều này rõ ràng hơn trong CDB / SOS:
0:000> .loadby sos mscorwks
0:000> !EEHeap -gc
Number of GC Heaps: 1
generation 0 starts at 0x00f7a9b0
generation 1 starts at 0x00e79c3c
generation 2 starts at 0x00b21000
ephemeral segment allocation context: none
segment begin allocated size
00b20000 00b21000 010029bc 0x004e19bc(5118396)
Large object heap starts at 0x01b21000
segment begin allocated size
01b20000 01b21000 01b8ade0 0x00069de0(433632)
Total Size 0x54b79c(5552028)
------------------------------
GC Heap Size 0x54b79c(5552028)
Lấy một đoạn LOH kết xuất cho thấy mô hình tôi đã thấy trong ứng dụng bị rò rỉ:
0:000> !DumpHeap 01b21000 01b8ade0
...
01b8a120 793040bc 528
01b8a330 00175e88 16 Free
01b8a340 793040bc 528
01b8a550 00175e88 16 Free
01b8a560 793040bc 528
01b8a770 00175e88 16 Free
01b8a780 793040bc 528
01b8a990 00175e88 16 Free
01b8a9a0 793040bc 528
01b8abb0 00175e88 16 Free
01b8abc0 793040bc 528
01b8add0 00175e88 16 Free total 1568 objects
Statistics:
MT Count TotalSize Class Name
00175e88 784 12544 Free
793040bc 784 421088 System.Object[]
Total 1568 objects
Lưu ý rằng kích thước mảng đối tượng là 528 (thay vì 1056) vì máy trạm của tôi là 32 bit và máy chủ ứng dụng là 64 bit. Mảng đối tượng vẫn dài 128 phần tử.
Vì vậy, đạo lý của câu chuyện này là phải thực tập rất cẩn thận. Nếu chuỗi bạn đang thực hiện không được biết là thành viên của một tập hợp hữu hạn thì ứng dụng của bạn sẽ bị rò rỉ do sự phân mảnh của LOH, ít nhất là trong phiên bản 2 của CLR.
Trong trường hợp ứng dụng của chúng tôi, có mã chung trong đường dẫn mã giải mã hóa thực tập nhận dạng thực thể trong quá trình giải phóng: Tôi thực sự nghi ngờ đây là thủ phạm. Tuy nhiên, ý định của nhà phát triển rõ ràng là tốt vì họ muốn đảm bảo rằng nếu cùng một thực thể được giải mã nhiều lần thì chỉ một phiên bản của chuỗi định danh sẽ được duy trì trong bộ nhớ.