Anatomi "Kebocoran Ingatan"

173

Dalam perspektif .NET:

  • Apa yang dimaksud dengan kebocoran memori ?
  • Bagaimana Anda dapat menentukan apakah aplikasi Anda bocor? Apa pengaruhnya?
  • Bagaimana Anda bisa mencegah kebocoran memori?
  • Jika aplikasi Anda memiliki kebocoran memori, apakah itu hilang ketika proses keluar atau terbunuh? Atau apakah kebocoran memori dalam aplikasi Anda memengaruhi proses lain pada sistem bahkan setelah proses selesai?
  • Dan bagaimana dengan kode yang tidak dikelola yang diakses melalui COM Interop dan / atau P / Invoke?
huseyint
sumber

Jawaban:

110

Penjelasan terbaik yang pernah saya lihat adalah di Bab 7 dari Yayasan gratis e-book Pemrograman .

Pada dasarnya, di .NET kebocoran kehabisan memori terjadi ketika benda yang direferensikan telah di-root dan dengan demikian tidak dapat dikumpulkan dengan sampah. Ini terjadi secara tidak sengaja ketika Anda menyimpan referensi di luar ruang lingkup yang dimaksud.

Anda akan tahu bahwa Anda memiliki kebocoran ketika Anda mulai mendapatkan OutOfMemoryExceptions atau penggunaan memori Anda melampaui apa yang Anda harapkan ( PerfMon memiliki penghitung memori yang bagus).

Memahami model memori .NET adalah cara terbaik Anda untuk menghindarinya. Secara khusus, memahami cara kerja pengumpul sampah dan bagaimana referensi bekerja - lagi, saya merujuk Anda ke bab 7 dari e-book. Juga, waspadai perangkap umum, mungkin peristiwa yang paling umum. Jika objek A terdaftar untuk sebuah acara pada objek B , maka objek A akan bertahan sampai obyek B menghilang karena B memegang referensi ke A . Solusinya adalah dengan membatalkan registrasi acara Anda setelah selesai.

Tentu saja, profil memori yang baik akan memungkinkan Anda melihat grafik objek Anda dan menjelajahi nesting / referensi objek Anda untuk melihat dari mana referensi berasal dan objek root apa yang bertanggung jawab ( profil semut gerbang merah , JetBrains dotMemory, memprofiler benar-benar bagus pilihan, atau Anda dapat menggunakan WinDbg dan SOS hanya teks , tapi saya sangat merekomendasikan produk komersial / visual kecuali jika Anda seorang guru nyata).

Saya percaya kode yang tidak dikelola tunduk pada kebocoran memori yang khas, kecuali bahwa referensi bersama dikelola oleh pengumpul sampah. Saya bisa salah tentang poin terakhir ini.

Karl Seguin
sumber
11
Oh kamu suka buku kan? Saya telah melihat penulis muncul di stackoverflow dari waktu ke waktu.
Johnno Nolan
Beberapa objek .NET juga dapat melakukan root sendiri dan menjadi tidak dapat ditagih. Apa pun yang IDisposable harus dibuang karena ini.
Kyoryu
1
@kyoryu: Bagaimana objek melakukan root sendiri?
Andrei Rînea
2
@Andrei: Saya pikir Thread yang sedang berjalan mungkin adalah contoh terbaik dari objek rooting itu sendiri Objek yang menempatkan referensi ke dirinya sendiri di lokasi non-publik statis (seperti berlangganan acara statis, atau menerapkan singleton dengan inisialisasi bidang statis) mungkin juga telah berakar sendiri, karena tidak ada cara yang jelas untuk ... um ... "mencabutnya" dari tambatannya.
Jeffrey Hantin
@ Jeffry, ini adalah cara yang tidak biasa untuk menggambarkan apa yang terjadi dan saya menyukainya!
Exitos
35

Sebenarnya, kebocoran memori memakan memori yang "tidak lagi digunakan" oleh program.

"Tidak lagi digunakan" memiliki lebih dari satu makna, itu bisa berarti "tidak ada lagi referensi untuk itu", yaitu, benar-benar tidak dapat dipulihkan, atau itu bisa berarti, direferensikan, dipulihkan, tidak digunakan tetapi program tetap menyimpan referensi. Hanya nanti yang berlaku untuk .Net untuk objek yang dikelola dengan sempurna . Namun, tidak semua kelas sempurna dan pada titik tertentu implementasi yang tidak dikelola yang mendasari dapat membocorkan sumber daya secara permanen untuk proses itu.

Dalam semua kasus, aplikasi mengkonsumsi lebih banyak memori daripada yang dibutuhkan. Efek samping, tergantung pada jumlah yang bocor, bisa berubah dari tidak ada, ke pelambatan yang disebabkan oleh pengumpulan berlebihan, ke serangkaian pengecualian memori dan akhirnya kesalahan fatal diikuti oleh penghentian proses paksa.

Anda tahu aplikasi memiliki masalah memori saat pemantauan menunjukkan bahwa semakin banyak memori yang dialokasikan untuk proses Anda setelah setiap siklus pengumpulan sampah . Dalam kasus seperti itu, Anda menyimpan terlalu banyak memori, atau beberapa implementasi yang tidak dikelola yang mendasari bocor.

Untuk sebagian besar kebocoran, sumber daya dipulihkan saat proses dihentikan, namun beberapa sumber daya tidak selalu dipulihkan dalam beberapa kasus tertentu, pegangan kursor GDI terkenal karena hal itu. Tentu saja, jika Anda memiliki mekanisme komunikasi antarproses, memori yang dialokasikan dalam proses lain tidak akan dibebaskan sampai proses itu membebaskannya atau berakhir.

Coincoin
sumber
32

Saya pikir pertanyaan "apa itu kebocoran memori" dan "apa efeknya" telah dijawab dengan baik, tetapi saya ingin menambahkan beberapa hal lagi pada pertanyaan lain ...

Bagaimana memahami apakah aplikasi Anda bocor

Salah satu cara yang menarik adalah membuka perfmon dan menambahkan jejak # byte di semua tumpukan dan koleksi # Gen 2 , dalam setiap kasus hanya melihat proses Anda. Jika menjalankan fitur tertentu menyebabkan total byte meningkat, dan memori itu tetap dialokasikan setelah koleksi Gen 2 berikutnya, Anda mungkin mengatakan bahwa fitur tersebut membocorkan memori.

Bagaimana mencegahnya

Pendapat baik lainnya telah diberikan. Saya hanya akan menambahkan bahwa mungkin penyebab paling umum dari kebocoran memori .NET adalah untuk menambahkan event handler ke objek tanpa menghapusnya. Pengatur kejadian yang dilampirkan pada objek adalah bentuk referensi ke objek tersebut, sehingga akan mencegah pengumpulan bahkan setelah semua referensi lain telah hilang. Selalu ingat untuk melepaskan event handler (menggunakan -=sintaks dalam C #).

Apakah kebocoran hilang ketika proses keluar, dan bagaimana dengan COM interop?

Ketika proses Anda keluar, semua memori yang dipetakan ke dalam ruang alamatnya diklaim kembali oleh OS, termasuk objek COM yang dilayani dari DLL. Relatif jarang, objek COM dapat dilayani dari proses yang terpisah. Dalam hal ini, ketika proses Anda keluar, Anda mungkin masih bertanggung jawab atas memori yang dialokasikan dalam setiap proses server COM yang Anda gunakan.

Martin
sumber
19

Saya akan mendefinisikan kebocoran memori sebagai objek yang tidak membebaskan semua memori yang dialokasikan setelah selesai. Saya telah menemukan ini dapat terjadi dalam aplikasi Anda jika Anda menggunakan Windows API dan COM (yaitu kode tidak dikelola yang memiliki bug di dalamnya atau tidak dikelola dengan benar), dalam kerangka kerja dan komponen pihak ketiga. Saya juga menemukan tidak beres setelah menggunakan benda-benda tertentu seperti pena dapat menyebabkan masalah.

Saya pribadi telah menderita Pengecualian Kehabisan Memori yang dapat disebabkan tetapi tidak eksklusif untuk kebocoran memori dalam aplikasi dot net. (OOM juga bisa berasal dari menjepit, lihat Pinning Artical ). Jika Anda tidak mendapatkan kesalahan OOM atau perlu mengkonfirmasi apakah ini merupakan kebocoran memori yang menyebabkannya, satu-satunya cara adalah dengan membuat profil aplikasi Anda.

Saya juga akan mencoba dan memastikan hal berikut:

a) Segala sesuatu yang menerapkan Idisposable dibuang baik menggunakan blok akhirnya atau pernyataan menggunakan ini termasuk kuas, pena dll (beberapa orang berpendapat untuk mengatur semuanya menjadi tidak ada tambahan)

b) Apa pun yang memiliki metode tutup ditutup lagi menggunakan akhirnya atau menggunakan pernyataan (meskipun saya telah menemukan menggunakan tidak selalu tutup tergantung jika Anda menyatakan objek di luar pernyataan menggunakan)

c) Jika Anda menggunakan kode / windows API yang tidak dikelola yang ditangani setelahnya dengan benar. (beberapa memiliki metode pembersihan untuk melepaskan sumber daya)

Semoga ini membantu.

John
sumber
19

Jika Anda perlu mendiagnosis kebocoran memori di .NET, periksa tautan ini:

http://msdn.microsoft.com/en-us/magazine/cc163833.aspx

http://msdn.microsoft.com/en-us/magazine/cc164138.aspx

Artikel-artikel tersebut menjelaskan cara membuat dump memori dari proses Anda dan bagaimana menganalisisnya sehingga Anda dapat menentukan terlebih dahulu apakah kebocoran Anda tidak dikelola atau dikelola, dan jika dikelola, bagaimana mencari tahu dari mana asalnya.

Microsoft juga memiliki alat yang lebih baru untuk membantu menghasilkan crash dumps, untuk menggantikan ADPlus, yang disebut DebugDiag.

http://www.microsoft.com/downloads/details.aspx?FamilyID=28bd5941-c458-46f1-b24d-f60151d875a3&displaylang=en

Eric Z Beard
sumber
15

Penjelasan terbaik tentang cara kerja pemulung adalah di Jeff Richters CLR via C # book, (Bab 20). Membaca ini memberikan landasan yang bagus untuk memahami bagaimana objek bertahan.

Salah satu penyebab paling umum dari rooting objek secara tidak sengaja adalah dengan menghubungkan berbagai peristiwa di luar kelas. Jika Anda menghubungkan acara eksternal

misalnya

SomeExternalClass.Changed += new EventHandler(HandleIt);

dan lupa melepaskan kaitannya ketika Anda membuang, maka SomeExternalClass memiliki referensi ke kelas Anda.

Seperti yang disebutkan di atas, profil memori SciTech sangat bagus untuk menunjukkan kepada Anda akar objek yang Anda curigai bocor.

Tetapi ada juga cara yang sangat cepat untuk memeriksa jenis tertentu hanya menggunakan WnDBG (Anda bahkan dapat menggunakan ini di jendela langsung VS.NET saat terpasang):

.loadby sos mscorwks
!dumpheap -stat -type <TypeName>

Sekarang lakukan sesuatu yang Anda pikir akan membuang objek jenis itu (mis. Tutup jendela). Berguna di sini untuk memiliki tombol debug di suatu tempat yang akan berjalan System.GC.Collect()beberapa kali.

Kemudian jalankan !dumpheap -stat -type <TypeName>lagi. Jika jumlahnya tidak turun, atau tidak turun sebanyak yang Anda harapkan, maka Anda memiliki dasar untuk penyelidikan lebih lanjut. (Saya mendapat tip ini dari seminar yang diberikan oleh Ingo Rammer ).

Gus Paul
sumber
14

Saya kira dalam lingkungan yang terkelola, kebocoran adalah Anda menyimpan referensi yang tidak perlu ke sejumlah besar memori di sekitar.

Bernard
sumber
11

Mengapa orang berpikir bahwa kebocoran memori di .NET tidak sama dengan kebocoran lainnya?

Kebocoran memori adalah ketika Anda melampirkan ke sumber daya dan jangan biarkan itu pergi. Anda dapat melakukan ini baik dalam kode terkelola dan tidak terkelola.

Mengenai .NET, dan alat pemrograman lain, ada ide tentang pengumpulan sampah, dan cara lain untuk meminimalkan situasi yang akan membuat aplikasi Anda bocor. Tetapi metode terbaik untuk mencegah kebocoran memori adalah Anda perlu memahami model memori yang mendasarinya, dan cara kerja berbagai hal, pada platform yang Anda gunakan.

Percaya bahwa GC dan sihir lain akan membersihkan kekacauan Anda adalah cara singkat untuk kebocoran memori, dan akan sulit ditemukan nanti.

Ketika coding tidak dikelola, Anda biasanya memastikan untuk membersihkan, Anda tahu bahwa sumber daya yang Anda pegang, akan menjadi tanggung jawab Anda untuk membersihkan, bukan milik petugas kebersihan.

Di NET. Di sisi lain, banyak orang berpikir bahwa GC akan membersihkan semuanya. Ya, memang ada gunanya bagi Anda, tetapi Anda perlu memastikan bahwa memang demikian. .NET membungkus banyak hal, jadi Anda tidak selalu tahu apakah Anda berhadapan dengan sumber daya yang dikelola atau tidak dikelola, dan Anda perlu memastikan apa yang Anda hadapi. Menangani font, sumber daya GDI, direktori aktif, basis data dll adalah hal-hal yang perlu Anda perhatikan.

Dalam istilah yang dikelola saya akan meletakkan leher saya di telepon untuk mengatakan itu hilang begitu proses dibunuh / dihapus.

Saya melihat banyak orang memiliki ini, dan saya sangat berharap ini akan berakhir. Anda tidak dapat meminta pengguna untuk menghentikan aplikasi Anda untuk membersihkan kekacauan Anda! Lihatlah browser, yang bisa berupa IE, FF dll, lalu buka, katakanlah, Google Reader, biarkan selama beberapa hari, dan lihat apa yang terjadi.

Jika Anda kemudian membuka tab lain di browser, berselancar ke beberapa situs, lalu tutup tab yang meng-host halaman lain yang membuat peramban bocor, apakah menurut Anda browser akan melepaskan memori? Tidak demikian halnya dengan IE. Di komputer saya, IE akan dengan mudah memakan memori 1 GiB dalam waktu singkat (sekitar 3-4 hari) jika saya menggunakan Google Reader. Beberapa surat kabar bahkan lebih buruk.

neslekkiM
sumber
10

Saya kira dalam lingkungan yang terkelola, kebocoran adalah Anda menyimpan referensi yang tidak perlu ke sejumlah besar memori di sekitar.

Benar. Juga, tidak menggunakan metode .dispose () pada objek sekali pakai yang sesuai dapat menyebabkan kebocoran mem. Cara termudah untuk melakukannya adalah dengan menggunakan blok karena secara otomatis mengeksekusi .Dispose () di akhir:

StreamReader sr;
using(sr = new StreamReader("somefile.txt"))
{
    //do some stuff
}

Dan jika Anda membuat kelas yang menggunakan objek yang tidak dikelola, jika Anda tidak menerapkan IDisposable dengan benar, Anda bisa menyebabkan kebocoran memori untuk pengguna kelas Anda.

Seibar
sumber
9

Semua kebocoran memori diselesaikan dengan penghentian program.

Memori yang cukup bocor dan Sistem Operasi dapat memutuskan untuk menyelesaikan masalah atas nama Anda.

Josh
sumber
8

Saya akan setuju dengan Bernard tentang. Bersih apa kebocoran mem.

Anda dapat membuat profil aplikasi Anda untuk melihat penggunaan memorinya, dan menentukan bahwa jika mengelola banyak memori ketika seharusnya tidak, Anda bisa mengatakan itu memiliki kebocoran.

Dalam istilah yang dikelola saya akan meletakkan leher saya di telepon untuk mengatakan itu hilang begitu proses dibunuh / dihapus.

Kode yang tidak dikelola adalah binatang buasnya sendiri dan jika ada kebocoran di dalamnya, ia akan mengikuti kode standar. definisi kebocoran.

Menepuk
sumber
7

Juga perlu diingat bahwa .NET memiliki dua tumpukan, satu menjadi tumpukan objek besar. Saya percaya benda sekitar 85k atau lebih besar diletakkan di tumpukan ini. Tumpukan ini memiliki aturan seumur hidup yang berbeda dari tumpukan biasa.

Jika Anda membuat struktur memori yang besar (Kamus atau Daftar), akan lebih bijaksana untuk mencari apa aturan sebenarnya.

Sejauh reklamasi memori pada proses terminasi, kecuali jika Anda menjalankan Win98 atau yang setara, semuanya dilepaskan kembali ke OS pada terminasi. Satu-satunya pengecualian adalah hal-hal yang dibuka lintas proses dan proses lain masih memiliki sumber daya terbuka.

Objek COM bisa rumit tho. Jika Anda selalu menggunakan IDisposepolanya, Anda akan aman. Tapi saya sudah menemukan beberapa majelis interop yang mengimplementasikan IDispose. Kuncinya di sini adalah menelepon Marshal.ReleaseCOMObjectsaat Anda selesai. Objek COM masih menggunakan penghitungan referensi COM standar.

Joel Lucsy
sumber
6

Saya menemukan Net Memory Profiler. Bantuan yang sangat baik ketika menemukan kebocoran memori di .Net. Ini tidak gratis seperti Microsoft CLR Profiler, tetapi lebih cepat dan lebih tepatnya menurut saya. SEBUAH

Lars Truijens
sumber