Men-debug kebocoran memori di .NET

Artikel ini berlaku untuk: ✔️ .NET Core 3.1 SDK dan versi yang lebih baru

Memori dapat bocor saat aplikasi Anda mereferensikan objek yang tidak lagi perlu melakukan tugas yang diinginkan. Mereferensikan objek ini mencegah pengumpul sampah mendapatkan kembali memori yang digunakan. Itu dapat mengakibatkan penurunan performa dan OutOfMemoryException pengecualian dilemparkan.

Tutorial ini menunjukkan alat untuk menganalisis kebocoran memori di aplikasi .NET menggunakan alat CLI diagnostik .NET. Jika Menggunakan Windows, Anda mungkin dapat menggunakan alat Diagnostik Memori Visual Studio untuk men-debug kebocoran memori.

Tutorial ini menggunakan aplikasi sampel yang sengaja membocorkan memori, sebagai latihan. Anda juga dapat menganalisis aplikasi yang secara tidak sengaja membocorkan memori.

Dalam tutorial ini, Anda akan:

  • Periksa penggunaan memori terkelola dengan penghitung dotnet.
  • Buat file cadangan.
  • Analisis penggunaan memori menggunakan file cadangan.

Prasyarat

Tutorial ini menggunakan:

Tutorial mengasumsikan aplikasi dan alat sampel diinstal dan siap digunakan.

Memeriksa penggunaan memori terkelola

Sebelum Anda mulai mengumpulkan data diagnostik untuk membantu akar penyebab skenario ini, pastikan Anda benar-benar melihat kebocoran memori (pertumbuhan penggunaan memori). Anda dapat menggunakan alat penghitung dotnet untuk mengonfirmasinya.

Buka jendela konsol dan navigasikan ke direktori tempat Anda mengunduh dan membuka zip target debug sampel. Jalankan target:

dotnet run

Dari konsol terpisah, temukan ID proses:

dotnet-counters ps

Outputnya harus mirip dengan:

4807 DiagnosticScena /home/user/git/samples/core/diagnostics/DiagnosticScenarios/bin/Debug/netcoreapp3.0/DiagnosticScenarios

Sekarang, periksa penggunaan memori terkelola dengan alat penghitung dotnet. --refresh-interval menentukan jumlah detik antara refresh:

dotnet-counters monitor --refresh-interval 1 -p 4807

Output langsung harus mirip dengan:

Press p to pause, r to resume, q to quit.
    Status: Running

[System.Runtime]
    # of Assemblies Loaded                           118
    % Time in GC (since last GC)                       0
    Allocation Rate (Bytes / sec)                 37,896
    CPU Usage (%)                                      0
    Exceptions / sec                                   0
    GC Heap Size (MB)                                  4
    Gen 0 GC / sec                                     0
    Gen 0 Size (B)                                     0
    Gen 1 GC / sec                                     0
    Gen 1 Size (B)                                     0
    Gen 2 GC / sec                                     0
    Gen 2 Size (B)                                     0
    LOH Size (B)                                       0
    Monitor Lock Contention Count / sec                0
    Number of Active Timers                            1
    ThreadPool Completed Work Items / sec             10
    ThreadPool Queue Length                            0
    ThreadPool Threads Count                           1
    Working Set (MB)                                  83

Fokus pada baris ini:

    GC Heap Size (MB)                                  4

Anda dapat melihat bahwa memori tumpukan terkelola adalah 4 MB tepat setelah startup.

Sekarang, buka URL https://localhost:5001/api/diagscenario/memleak/20000.

Amati bahwa penggunaan memori telah tumbuh menjadi 30 MB.

    GC Heap Size (MB)                                 30

Dengan menonton penggunaan memori, Anda dapat dengan aman mengatakan bahwa memori tumbuh atau bocor. Langkah selanjutnya adalah mengumpulkan data yang tepat untuk analisis memori.

Hasilkan cadangan memori

Saat menganalisis kemungkinan kebocoran memori, Anda memerlukan akses ke tumpuan memori aplikasi untuk menganalisis konten memori. Melihat hubungan antar objek, Anda membuat teori mengapa memori tidak dibebaskan. Sumber data diagnostik umum adalah cadangan memori di Windows atau cadangan inti yang setara di Linux. Untuk menghasilkan cadangan aplikasi .NET, Anda dapat menggunakan alat dotnet-dump .

Menggunakan target debug sampel yang sebelumnya dimulai, jalankan perintah berikut untuk menghasilkan cadangan inti Linux:

dotnet-dump collect -p 4807

Hasilnya adalah cadangan inti yang terletak di folder yang sama.

Writing minidump with heap to ./core_20190430_185145
Complete

Catatan

Untuk perbandingan dari waktu ke waktu, biarkan proses asli terus berjalan setelah mengumpulkan cadangan pertama dan mengumpulkan cadangan kedua dengan cara yang sama. Anda kemudian akan memiliki dua cadangan selama periode waktu yang dapat Anda bandingkan dengan melihat di mana penggunaan memori tumbuh.

Mulai ulang proses yang gagal

Setelah cadangan dikumpulkan, Anda harus memiliki informasi yang memadai untuk mendiagnosis proses yang gagal. Jika proses yang gagal berjalan di server produksi, sekarang ini adalah waktu yang ideal untuk remediasi jangka pendek dengan memulai ulang proses.

Dalam tutorial ini, Anda sekarang sudah selesai dengan target Debug Sampel dan Anda dapat menutupnya. Navigasi ke terminal yang memulai server, dan tekan Ctrl+C.

Menganalisis cadangan inti

Sekarang setelah Anda memiliki cadangan inti yang dihasilkan, gunakan alat dotnet-dump untuk menganalisis cadangan:

dotnet-dump analyze core_20190430_185145

Di mana core_20190430_185145 adalah nama core dump yang ingin Anda analisis.

Catatan

Jika Anda melihat kesalahan yang mengeluh bahwa libdl.so tidak dapat ditemukan, Anda mungkin harus menginstal paket libc6-dev . Untuk informasi selengkapnya, lihat Prasyarat untuk .NET di Linux.

Anda akan disajikan dengan perintah di mana Anda dapat memasukkan perintah SOS . Umumnya, hal pertama yang ingin Anda lihat adalah keadaan keseluruhan tumpukan terkelola:

> dumpheap -stat

Statistics:
              MT    Count    TotalSize Class Name
...
00007f6c1eeefba8      576        59904 System.Reflection.RuntimeMethodInfo
00007f6c1dc021c8     1749        95696 System.SByte[]
00000000008c9db0     3847       116080      Free
00007f6c1e784a18      175       128640 System.Char[]
00007f6c1dbf5510      217       133504 System.Object[]
00007f6c1dc014c0      467       416464 System.Byte[]
00007f6c21625038        6      4063376 testwebapi.Controllers.Customer[]
00007f6c20a67498   200000      4800000 testwebapi.Controllers.Customer
00007f6c1dc00f90   206770     19494060 System.String
Total 428516 objects

Di sini Anda dapat melihat bahwa sebagian besar objek adalah objek String atau Customer .

Anda dapat menggunakan dumpheap perintah lagi dengan tabel metode (MT) untuk mendapatkan daftar semua String instans:

> dumpheap -mt 00007f6c1dc00f90

         Address               MT     Size
...
00007f6ad09421f8 00007faddaa50f90       94
...
00007f6ad0965b20 00007f6c1dc00f90       80
00007f6ad0965c10 00007f6c1dc00f90       80
00007f6ad0965d00 00007f6c1dc00f90       80
00007f6ad0965df0 00007f6c1dc00f90       80
00007f6ad0965ee0 00007f6c1dc00f90       80

Statistics:
              MT    Count    TotalSize Class Name
00007f6c1dc00f90   206770     19494060 System.String
Total 206770 objects

Anda sekarang dapat menggunakan gcroot perintah pada System.String instans untuk melihat bagaimana dan mengapa objek berakar:

> gcroot 00007f6ad09421f8

Thread 3f68:
    00007F6795BB58A0 00007F6C1D7D0745 System.Diagnostics.Tracing.CounterGroup.PollForValues() [/_/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/CounterGroup.cs @ 260]
        rbx:  (interior)
            ->  00007F6BDFFFF038 System.Object[]
            ->  00007F69D0033570 testwebapi.Controllers.Processor
            ->  00007F69D0033588 testwebapi.Controllers.CustomerCache
            ->  00007F69D00335A0 System.Collections.Generic.List`1[[testwebapi.Controllers.Customer, DiagnosticScenarios]]
            ->  00007F6C000148A0 testwebapi.Controllers.Customer[]
            ->  00007F6AD0942258 testwebapi.Controllers.Customer
            ->  00007F6AD09421F8 System.String

HandleTable:
    00007F6C98BB15F8 (pinned handle)
    -> 00007F6BDFFFF038 System.Object[]
    -> 00007F69D0033570 testwebapi.Controllers.Processor
    -> 00007F69D0033588 testwebapi.Controllers.CustomerCache
    -> 00007F69D00335A0 System.Collections.Generic.List`1[[testwebapi.Controllers.Customer, DiagnosticScenarios]]
    -> 00007F6C000148A0 testwebapi.Controllers.Customer[]
    -> 00007F6AD0942258 testwebapi.Controllers.Customer
    -> 00007F6AD09421F8 System.String

Found 2 roots.

Anda dapat melihat bahwa String langsung dipegang oleh Customer objek dan secara tidak langsung dipegang oleh CustomerCache objek.

Anda dapat terus mencadangkan objek untuk melihat bahwa sebagian besar String objek mengikuti pola yang sama. Pada titik ini, investigasi memberikan informasi yang memadai untuk mengidentifikasi akar penyebab dalam kode Anda.

Prosedur umum ini memungkinkan Anda mengidentifikasi sumber kebocoran memori utama.

Membersihkan sumber daya

Dalam tutorial ini, Anda memulai contoh server web. Server ini seharusnya dimatikan seperti yang dijelaskan di bagian Mulai ulang proses yang gagal.

Anda juga dapat menghapus file cadangan yang dibuat.

Baca juga

Langkah berikutnya