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.
sumber
Jawaban:
CLR menggunakan LOH untuk mengalokasikan beberapa objek (seperti array yang digunakan untuk string internal ). Beberapa di antaranya kurang dari 85000 byte dan oleh karena itu biasanya tidak akan dialokasikan pada LOH.
Ini adalah detail implementasi, tetapi saya berasumsi alasannya adalah untuk menghindari pengumpulan sampah yang tidak perlu dari contoh yang seharusnya bertahan selama prosesnya sendiri.
Juga karena optimasi yang agak esoterik, salah
double[]
satu dari 1000 atau lebih elemen juga dialokasikan pada LOH.sumber
GC.GetGeneration
instans. Ini akan mengembalikan Gen2 - API tidak membedakan antara Gen2 dan LOH. Buat array satu byte lebih kecil dan API akan mengembalikan Gen0..NET Framework 4.5.1, memiliki kemampuan untuk secara eksplisit memadatkan tumpukan objek besar (LOH) selama pengumpulan sampah.
Lihat info selengkapnya di GCSettings.LargeObjectHeapCompactionMode
sumber
Saat membaca deskripsi tentang cara kerja GC, dan bagian tentang bagaimana objek berumur panjang berakhir di generasi 2, dan pengumpulan objek LOH terjadi hanya pada koleksi lengkap - seperti halnya koleksi generasi 2, gagasan yang muncul di benak adalah. .. mengapa tidak menyimpan generasi 2 dan objek besar di heap yang sama, karena mereka akan dikumpulkan bersama?
Jika itu yang sebenarnya terjadi, maka itu akan menjelaskan bagaimana benda-benda kecil berakhir di tempat yang sama dengan LOH - jika mereka berumur cukup lama untuk berakhir di generasi 2.
Jadi masalah Anda tampaknya merupakan sanggahan yang cukup bagus untuk ide yang muncul pada saya - ini akan mengakibatkan fragmentasi LOH.
Ringkasan: masalah Anda dapat dijelaskan oleh LOH dan generasi 2 yang berbagi wilayah heap yang sama, meskipun itu sama sekali bukan bukti bahwa ini adalah penjelasannya.
Pembaruan: keluaran dari
!dumpheap -stat
cukup banyak pukulan teori ini keluar dari air! Generasi 2 dan LOH memiliki wilayahnya masing-masing.sumber
double
array yang lebih besar dari 1000 elemen pada LOH, daripada mengubah GC untuk memastikan bahwa mereka selaras pada batas 8-byte. Sebenarnya, bahkan pada sistem 32-bit saya berharap bahwa karena perilaku cache, memaksakan penyelarasan 8-byte pada semua objek yang dialokasikan ukuran kelipatan 8 byte mungkin akan menjadi kemenangan kinerja. Jika tidak, sementara kinerja yang banyak digunakandouble[]
yang selaras dengan cache akan lebih baik daripada yang tidak, saya tidak tahu mengapa ukuran akan berkorelasi dengan penggunaan.Jika format dikenali sebagai aplikasi Anda, mengapa Anda belum mengidentifikasi kode yang menghasilkan format string ini? Jika ada beberapa kemungkinan, coba tambahkan data unik untuk mencari tahu jalur kode mana yang menjadi penyebabnya.
Fakta bahwa array disisipkan dengan item besar yang dibebaskan membuat saya menebak bahwa mereka awalnya berpasangan atau setidaknya terkait. Cobalah untuk mengidentifikasi objek yang dibebaskan untuk mencari tahu apa yang menghasilkannya dan string terkait.
Setelah Anda mengidentifikasi apa yang menghasilkan string ini, cobalah untuk mencari tahu apa yang membuat mereka tidak di-GC. Mungkin mereka dimasukkan ke dalam daftar yang terlupakan atau tidak terpakai untuk tujuan logging atau yang serupa.
EDIT: Abaikan wilayah memori dan ukuran array tertentu untuk saat ini: cari tahu apa yang dilakukan dengan string ini yang menyebabkan kebocoran. Coba! GCRoot ketika program Anda telah membuat atau memanipulasi string ini hanya sekali atau dua kali, saat objek yang harus dilacak lebih sedikit.
sumber
Pertanyaan bagus, saya belajar dengan membaca pertanyaan.
Saya pikir bit lain dari jalur kode deserialisation juga menggunakan tumpukan objek besar, maka fragmentasi. Jika semua string diinternir pada waktu yang sama, saya pikir Anda akan baik-baik saja.
Mengingat seberapa baik pengumpul sampah .net, membiarkan jalur kode deserialisation membuat objek string normal kemungkinan sudah cukup baik. Jangan melakukan sesuatu yang lebih rumit sampai kebutuhan itu terbukti.
Saya paling banyak akan melihat tabel hash dari beberapa string terakhir yang telah Anda lihat dan menggunakan kembali ini. Dengan membatasi ukuran tabel hash dan meneruskan ukuran saat Anda membuat tabel, Anda dapat menghentikan sebagian besar fragmentasi. Anda kemudian membutuhkan cara untuk menghapus string yang belum lama ini Anda lihat dari tabel hash untuk membatasi ukurannya. Tetapi jika string yang dibuat oleh jalur kode deserialisation berumur pendek, Anda tidak akan mendapatkan banyak jika ada.
sumber
Berikut adalah beberapa cara untuk Mengidentifikasi tumpukan panggilan yang tepat dari alokasi LOH .
Dan untuk menghindari fragmentasi LOH Alokasikan terlebih dahulu berbagai objek dan pin mereka. Gunakan kembali benda-benda ini saat dibutuhkan. Berikut adalah posting tentang Fragmentasi LOH. Sesuatu seperti ini dapat membantu menghindari fragmentasi LOH.
sumber
Menurut http://webcache.googleusercontent.com/search?q=cache:l3Ppy_eFoAAJ:www.wintellect.com/devcenter/sloscialo/hey-who-stole-all-my-memory+&cd=5&hl=id&ct=clnk&gl= mx & client = firefox-b
sumber