Aplikasi C # / .NET yang saya kerjakan mengalami kebocoran memori yang lambat. Saya telah menggunakan CDB dengan SOS untuk mencoba menentukan apa yang terjadi tetapi datanya tampaknya tidak masuk akal jadi saya berharap salah satu dari Anda mungkin pernah mengalami ini sebelumnya.
Aplikasi ini berjalan pada kerangka 64 bit. Ini terus menghitung dan menserialisasikan data ke host jarak jauh dan mengenai Large Object Heap (LOH) sedikit lebih baik. Namun, sebagian besar objek LOH yang saya harapkan bersifat sementara: setelah penghitungan selesai dan telah dikirim ke host jarak jauh, memori harus dibebaskan. Apa yang saya lihat, bagaimanapun, adalah sejumlah besar array objek (hidup) yang disisipkan dengan blok memori bebas, misalnya, mengambil segmen acak dari 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
...
Jelas saya mengharapkan hal ini terjadi jika aplikasi saya membuat objek besar yang berumur panjang selama setiap perhitungan. (Itu melakukan ini dan saya menerima akan ada tingkat fragmentasi LOH tetapi itu bukan masalah di sini.) Masalahnya adalah array objek yang sangat kecil (1056 byte) yang dapat Anda lihat di dump di atas yang tidak dapat saya lihat dalam kode sedang dibuat dan yang entah bagaimana tetap berakar.
Perhatikan juga bahwa CDB tidak melaporkan tipe saat segmen tumpukan dibuang: Saya tidak yakin apakah ini terkait atau tidak. Jika saya membuang objek yang ditandai (<-), CDB / SOS melaporkannya dengan baik:
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
Elemen-elemen dari larik objek adalah semua string dan string dapat dikenali dari kode aplikasi kita.
Juga, saya tidak dapat menemukan akar GC mereka karena perintah! GCRoot hang dan tidak pernah kembali (saya bahkan mencoba meninggalkannya dalam semalam).
Jadi, saya akan sangat menghargai jika ada yang bisa menjelaskan mengapa array objek kecil (<85k) ini berakhir di LOH: situasi apa yang akan. NET menempatkan array objek kecil di sana? Juga, adakah yang kebetulan mengetahui cara alternatif untuk memastikan akar dari benda-benda ini?
Perbarui 1
Teori lain yang saya kemukakan kemarin adalah bahwa larik objek ini dimulai dengan ukuran besar tetapi telah menyusut meninggalkan blok memori bebas yang terbukti dalam dump memori. Yang membuat saya curiga adalah bahwa array objek selalu tampak sepanjang 1056 byte (128 elemen), 128 * 8 untuk referensi dan 32 byte overhead.
Idenya adalah bahwa mungkin beberapa kode yang tidak aman di perpustakaan atau di CLR merusak bidang jumlah elemen di header array. Agak jauh aku tahu ...
Perbarui 2
Berkat Brian Rasmussen (lihat jawaban yang diterima), masalah telah diidentifikasi sebagai fragmentasi LOH yang disebabkan oleh tabel intern string! Saya menulis aplikasi tes cepat untuk mengonfirmasi ini:
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();
}
Aplikasi pertama kali membuat dan membedakan string unik dalam satu lingkaran. Ini hanya untuk membuktikan bahwa memori tidak bocor dalam skenario ini. Jelas seharusnya tidak dan tidak.
Di loop kedua, string unik dibuat dan disimpan. Tindakan ini mengakar mereka di tabel magang. Yang tidak saya sadari adalah bagaimana tabel magang direpresentasikan. Tampaknya itu terdiri dari satu set halaman - array objek dari 128 elemen string - yang dibuat di LOH. Ini lebih jelas di 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)
Mengambil dump segmen LOH mengungkapkan pola yang saya lihat dalam aplikasi yang bocor:
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
Perhatikan bahwa ukuran array objek adalah 528 (bukan 1056) karena workstation saya 32 bit dan server aplikasi 64 bit. Array objek masih 128 elemen.
Jadi moral dari cerita ini adalah dengan sangat berhati-hati saat magang. Jika string yang Anda interning tidak diketahui sebagai anggota dari himpunan terbatas maka aplikasi Anda akan bocor karena fragmentasi LOH, setidaknya di CLR versi 2.
Dalam kasus aplikasi kita, ada kode umum di jalur kode deserialisation yang memasukkan pengenal entitas selama unmarshalling: Saya sekarang sangat curiga ini pelakunya. Namun, niat pengembang jelas bagus karena mereka ingin memastikan bahwa jika entitas yang sama dideserialisasikan beberapa kali maka hanya satu contoh string pengenal yang akan dipertahankan dalam memori.