Поиск утечек памяти в С#

В следующей программе размер начального размера памяти не восстанавливается, хотя выполняется сбор мусора. 1. Начальный размер памяти Общая память: 16,940 байт Частные байты 8134656

  • Созданные объекты в цикле, чтобы эти объекты были освобождены, когда gc collect выполняется вне цикла, поскольку эти объекты не имеют своей области.

  • Но память после сбора GC не совпадает с начальным размером Общая память: 29,476 байт Частные байты 8540160 Количество ручек: 115

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MemoryLeakTest
{

    class Program
    {
        static void DisplayMemory()
        {
            Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));            
            Console.WriteLine("Private bytes {0}", System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64);
            Console.WriteLine("Handle count: {0}", System.Diagnostics.Process.GetCurrentProcess().HandleCount);
            Console.WriteLine();
        }

        static void Main()
        {
            DisplayMemory();
            GC.Collect();
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("--- New object #{0} ---", i + 1);

                object o = new object();

                GC.Collect();
                DisplayMemory();
            }
            Console.WriteLine("--- press any key to quit ---");            
            Console.WriteLine();
            Console.Read();

            GC.Collect();
            DisplayMemory();
        }

    }
}

Output: 
=======
Total memory: 16,940 bytes
Private bytes 8134656
Handle count: 115

--- New object #1 ---
Total memory: 25,588 bytes
Private bytes 8540160
Handle count: 115

--- New object #2 ---
Total memory: 25,576 bytes
Private bytes 8540160
Handle count: 115

--- New object #3 ---
Total memory: 25,576 bytes
Private bytes 8540160
Handle count: 115

--- New object #4 ---
Total memory: 25,576 bytes
Private bytes 8540160
Handle count: 115

--- New object #5 ---
Total memory: 25,576 bytes
Private bytes 8540160
Handle count: 115

--- press any key to quit ---


Total memory: 29,476 bytes
Private bytes 8540160
Handle count: 115

*********************************

В чем причина увеличения частных байтов и размера управляемой кучи от их начального размера?

Ответы

Ответ 1

Общая проблема

Попытка подсчета и отчета об использовании памяти была обработана таким образом, что в 10 000 раз больше памяти, чем измеряемый размер объекта объекта GC.

Кроме того, печать кол-ва ссылок не применима к проблеме (поскольку в тесте не было открыто/закрыто дескрипторов), но это привело к значительному распределению памяти (удаление только этого счета сократило общие ассигнования в половина).

Исходная программа пыталась измерить выделение 60-120 байт объектов (в зависимости от того, была ли она 32 или 64-разрядной программой), но она делала это с использованием функций, которые вызывали выделение 600 КБ памяти каждый раз, когда они были, половина из которых находилась в куче больших объектов (LOH).

Предлагается альтернативный способ тестирования, который показывает, что все объекты действительно исчезли после вызова GC.Collect. Также приводятся сведения об использовании памяти в функции DisplayMemory.

Заключение

Размер управляемой памяти не увеличивается, когда создаются и собираются объекты 100k. Частные байты процесса увеличиваются примерно на 12 КБ, когда создаются и собираются только 5 объектов, но SoS показывает, что это не из управляемой кучи. Когда вы имеете дело с очень маленькими размерами и количеством объектов, вы не сможете точно определить, что происходит; вместо этого я предлагаю тестирование с очень большим количеством объектов, так что будет очень легко увидеть, что что-то просачивается. В этом случае нет утечки, ничего не получается, все в порядке.

Инструменты и подход анализа

Я использовал два инструмента для просмотра использования памяти этой программой:

  • VS 2013 Pro - инструмент для повышения производительности и диагностики. Сначала я запустил эту программу и увидел, что исходная программа выделяет 3,6 МБ памяти, а не только 60-120 байт, как и ожидалось от распределения объектов. Я знал, что какая-то память будет использоваться струнами и записываться на консоль, но 3,6 МБ был шоком.
  • Son of Strike (SoS) - это расширение отладчика, которое работает в Visual Studio и WinDbg, и оно поставляется с .Net Framework (см. файл sos.dll в каждом каталоге версий фреймворка на вашем компьютере).

VS 2013 Pro - Инструмент для повышения производительности и диагностики - Примечания

Ниже приведены результаты выполнения исходной программы в инструменте "Производительность и диагностика" в VS 2013 Pro с "методом профилирования", установленным в "Распределение памяти .NET". Это очень быстро подсказывало, что выделяется больше памяти, чем мысли. См. 3,6 МБ общих распределений над диаграммой. Если вы удалите вызовы DisplayMemory, которые упадут до 2476 байт.

Performance and Diagnostics Tool - Screenshot

Сын забастовки - Примечания

Вы можете использовать SoS в VS2010, если вы не установили .Net 4.5 на машине, или можете использовать его в VS2012 с Update3; просто убедитесь, что вы включили неуправляемую отладку в своем проекте и убедитесь, что вы запускаете 32-битный процесс, а затем запустите ".load sos" в окне "Немедленное окно" отладчика VS. Команды, которые я использовал для просмотра этой проблемы, были: "! Eeheap -gc" и "! Dumpheap -stat".

Альтернативная тестовая программа

class Program
{
    static void Main()
    {
        // A few objects get released by the initial GC.Collect call - the count drops from 108 to 94 objects in one test
        GC.Collect();

        // Set a breakpoint here, run these two sos commands:
        // !eeheap -gc
        // !dumpheap -stat
        for (int i = 0; i < 100000; i++)
        {
            object o = new object();
        }

        // Set a breakpoint here, run these two sos commands before this line, then step over and run them again
        // !eeheap -gc
        // !dumpheap -stat
        GC.Collect();
    }
}

Альтернативные результаты испытаний

Резюме

После выделения и сбора 100 000 System.Objects мы заканчиваем на 4 объекта меньше, чем мы начали, и размер управляемой кучи, который на 900 байт меньше, чем мы начали.

Сбор мусора работает должным образом.

Исходный уровень - после первого GC.Collect

!eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x024f23d0
generation 1 starts at 0x024f100c
generation 2 starts at 0x024f1000
ephemeral segment allocation context: none
         segment             begin         allocated  size
024f0000  024f1000  024f23dc  0x13dc(5084)
Large object heap starts at 0x034f1000
         segment             begin         allocated  size
034f0000  034f1000  034f5380  0x4380(17280)
Total Size:              Size: 0x575c (22364) bytes.
------------------------------
GC Heap Size:    Size: 0x575c (22364) bytes.

!dumpheap -stat
Statistics:
      MT    Count    TotalSize Class Name
[...]
6ed026b8        1          112 System.AppDomain
6ed025b0        2          168 System.Threading.ThreadAbortException
6ed05d3c        1          284 System.Collections.Generic.Dictionary`2+Entry[[System.Type, mscorlib],[System.Security.Policy.EvidenceTypeDescriptor, mscorlib]][]
6ed03a6c        2          380 System.Int32[]
6ed0349c       20          560 System.RuntimeType
0047fab8       14         1024      Free
6ed02248       32         1692 System.String
6ecefe88        6        17340 System.Object[]
Total 95 objects

После выделения 100 000 System.Objects, перед окончанием GC.Collect

!eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x024f23d0
generation 1 starts at 0x024f100c
generation 2 starts at 0x024f1000
ephemeral segment allocation context: none
         segment             begin         allocated  size
024f0000  024f1000  02617ff4  0x126ff4(1208308)
Large object heap starts at 0x034f1000
         segment             begin         allocated  size
034f0000  034f1000  034f5380  0x4380(17280)
Total Size:              Size: 0x12b374 (1225588) bytes.
------------------------------
GC Heap Size:    Size: 0x12b374 (1225588) bytes.

!dumpheap -stat
Statistics:
      MT    Count    TotalSize Class Name
[...]
6ed024e4        1           84 System.OutOfMemoryException
6ed02390        1           84 System.Exception
6ed026b8        1          112 System.AppDomain
6ed025b0        2          168 System.Threading.ThreadAbortException
6ed05d3c        1          284 System.Collections.Generic.Dictionary`2+Entry[[System.Type, mscorlib],[System.Security.Policy.EvidenceTypeDescriptor, mscorlib]][]
6ed03a6c        2          380 System.Int32[]
6ed0349c       20          560 System.RuntimeType
0047fab8       14         1024      Free
6ed02248       32         1692 System.String
6ecefe88        6        17340 System.Object[]
6ed025e8   100002      1200024 System.Object
Total 100095 objects

После окончательного GC.Collect

!eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x024f2048
generation 1 starts at 0x024f2030
generation 2 starts at 0x024f1000
ephemeral segment allocation context: none
         segment             begin         allocated  size
024f0000  024f1000  024f2054  0x1054(4180)
Large object heap starts at 0x034f1000
         segment             begin         allocated  size
034f0000  034f1000  034f5380  0x4380(17280)
Total Size:              Size: 0x53d4 (21460) bytes.
------------------------------
GC Heap Size:    Size: 0x53d4 (21460) bytes.

!dumpheap -stat
Statistics:
      MT    Count    TotalSize Class Name
[...]
6ed024e4        1           84 System.OutOfMemoryException
6ed02390        1           84 System.Exception
6ed026b8        1          112 System.AppDomain
0047fab8        9          118      Free
6ed025b0        2          168 System.Threading.ThreadAbortException
6ed05d3c        1          284 System.Collections.Generic.Dictionary`2+Entry[[System.Type, mscorlib],[System.Security.Policy.EvidenceTypeDescriptor, mscorlib]][]
6ed03a6c        2          380 System.Int32[]
6ed0349c       20          560 System.RuntimeType
6ed02248       32         1692 System.String
6ecefe88        6        17340 System.Object[]
Total 91 objects

Обзор использования памяти функции DisplayMemory

По сравнению с распределением System.Object, DisplayMemory - это зависание памяти. Он создает строки (которые идут в кучу), а функции, которые он вызывает для получения памяти, используют тонны (примерно 600 КБ) самой памяти.

Использование памяти перед вызовом DisplayMemory

!eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x02321018
generation 1 starts at 0x0232100c
generation 2 starts at 0x02321000
ephemeral segment allocation context: none
         segment             begin         allocated  size
02320000  02321000  02323ff4  0x2ff4(12276)
Large object heap starts at 0x03321000
         segment             begin         allocated  size
03320000  03321000  03325380  0x4380(17280)
Total Size:              Size: 0x7374 (29556) bytes.
------------------------------
GC Heap Size:    Size: 0x7374 (29556) bytes.

!dumpheap -stat
Statistics:
      MT    Count    TotalSize Class Name
[...]
6ed05d3c        3          468 System.Collections.Generic.Dictionary`2+Entry[[System.Type, mscorlib],[System.Security.Policy.EvidenceTypeDescriptor, mscorlib]][]
6ed0349c       20          560 System.RuntimeType
6ed02248       38         2422 System.String
6ecefe88        6        17340 System.Object[]
Total 102 objects

Использование памяти после вызова DisplayMemory

!eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x023224fc
generation 1 starts at 0x023224f0
generation 2 starts at 0x02321000
ephemeral segment allocation context: none
         segment             begin         allocated  size
02320000  02321000  02371ff4  0x50ff4(331764)
Large object heap starts at 0x03321000
         segment             begin         allocated  size
03320000  03321000  033653c0  0x443c0(279488)
Total Size:              Size: 0x953b4 (611252) bytes.
------------------------------
GC Heap Size:    Size: 0x953b4 (611252) bytes.

!dumpheap -stat
Statistics:
      MT    Count    TotalSize Class Name
[...]
6ed02c08        9          954 System.Char[]
006dfac0       17         1090      Free
6ed03aa4      156         1872 System.Int32
6ecffc20      152         3648 System.Collections.ArrayList
6ed05ed4        9         7776 System.Collections.Hashtable+bucket[]
7066e388      152        16416 System.Diagnostics.ProcessInfo
6ed02248      669        20748 System.String
706723e4      152        29184 System.Diagnostics.NtProcessInfoHelper+SystemProcessInformation
6ecefe88      463        48472 System.Object[]
706743a4     2104        75744 System.Diagnostics.ThreadInfo
70666568     2104       151488 System.Diagnostics.NtProcessInfoHelper+SystemThreadInformation
6ed0d640        2       262168 System.Int64[]
Total 6132 objects

Ответ 2

У меня есть следующие результаты:

Total memory: 94.804 bytes
Private bytes 19230720
Handle count: 252

--- New object #1 ---
Total memory: 96.932 bytes
Private bytes 19820544
Handle count: 252

--- New object #2 ---
Total memory: 96.932 bytes
Private bytes 19820544
Handle count: 252

--- New object #3 ---
Total memory: 96.932 bytes
Private bytes 19820544
Handle count: 252

--- New object #4 ---
Total memory: 96.932 bytes
Private bytes 19820544
Handle count: 252

--- New object #5 ---
Total memory: 96.932 bytes
Private bytes 19820544
Handle count: 252

--- press any key to quit ---
Total memory: 96.920 bytes
Private bytes 19820544
Handle count: 252

class Program
{
static void DisplayMemory()
{
    Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));
    Console.WriteLine("Private bytes {0}", System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64);
  Console.WriteLine("Handle count: {0}", System.Diagnostics.Process.GetCurrentProcess().HandleCount);
    Console.WriteLine();
 }

 static void Main()
 {
  DisplayMemory();
  GC.Collect();
  GC.WaitForPendingFinalizers();
  GC.Collect();

   for (int i = 0; i < 5; i++)
   {
     Console.WriteLine("--- New object #{0} ---", i + 1);

     object o = new object();

     GC.Collect();
     GC.WaitForPendingFinalizers();
     GC.Collect();

     DisplayMemory();
   }

   Console.WriteLine("--- press any key to quit ---");
  //Console.ReadLine();

  GC.Collect();
  GC.WaitForPendingFinalizers();
  GC.Collect();

  GC.WaitForFullGCComplete();
  DisplayMemory();

  Console.ReadLine();
}

Я просто удалил Console.ReadLine(); и добавил GC.WaitForPendingFinalizers();. Теперь код, работающий по вашему желанию, кажется Console.ReadLine() не освобождает ресурсы напрямую!

Ответ 3

GC.Collect является асинхронным, поэтому логика вызова DisplayMemory() прямо после GC.Collect(); вызывает сомнения. Запустить GC.Collect синхронно содержит некоторые подробности о GC.WaitForPendingFinalizers().

Изменить - в ответ на ваш вопрос:

Total memory: 84,280 bytes
Private bytes 15384576
Handle count: 245

--- New object #1 ---
Total memory: 86,408 bytes
Private bytes 15908864
Handle count: 245

--- New object #2 ---
Total memory: 86,408 bytes
Private bytes 15908864
Handle count: 245

--- New object #3 ---
Total memory: 86,408 bytes
Private bytes 15908864
Handle count: 245

--- New object #4 ---
Total memory: 86,408 bytes
Private bytes 15908864
Handle count: 245

--- New object #5 ---
Total memory: 86,408 bytes
Private bytes 15908864
Handle count: 245

Значит, здесь это не очень важно!

Ответ 4

Общая память: 160,144 байт Частные байты 27189248 Количество ручек: 247

--- Новый объект # 1 --- s Общая память: 160,152 байт Частные байты 27979776 Количество ручек: 247

--- Новый объект # 2 --- s Общая память: 160,152 байт Частные байты 27979776 Количество ручек: 247

--- Новый объект # 3 --- Общая память: 160,152 байт Частные байты 27979776 Количество ручек: 247

--- Новый объект # 4 --- s Общая память: 160,152 байт Частные байты 27979776 Количество ручек: 247

--- Новый объект # 5 --- s Общая память: 160,152 байт Частные байты 27975680 Количество ручек: 247

--- нажмите любую клавишу, чтобы выйти ---

Общая память: 160,152 байт Частные байты 27996160 Количество ручек: 247

    static void Main()
    {
        DisplayMemory();
        GC.Collect();
        GC.WaitForFullGCComplete();
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine("--- New object #{0} ---", i + 1);

            object o = new object();

            GC.Collect();
            GC.WaitForFullGCComplete();
            Console.Read();
            Console.Read();
            DisplayMemory();

        }
        Console.WriteLine("--- press any key to quit ---");
        Console.WriteLine();
        Console.Read();
        Console.Read();

        GC.Collect();
        GC.WaitForFullGCComplete();
        DisplayMemory();
    }

Ответ 5

Я проверил ваш код. Первые два вывода, которые вы получили, следующие:

Output: 
=======
Total memory: 16,940 bytes
Private bytes 8134656
Handle count: 115

--- New object #1 ---
Total memory: 25,588 bytes
Private bytes 8540160
Handle count: 115

Здесь значительно увеличивается использование памяти, и это связано с использованием статического класса Console (Console.WriteLine) в методе DisplayMemory(). Это происходит не потому, что вы создаете объект в цикле несколько раз. Создание объекта в цикле создает только 120 байт в общей памяти, но использование класса Console использует примерно 8000 байт в общей памяти. Попробуйте следующий код, и вы поймете.

static void Main()
        {
            DisplayMemory();
            List<object> objList = new List<object>();
            for (int i = 0; i < 15; i++)
            {
                Console.WriteLine("--- New object #{0} ---", i + 1);

                object o = new object();
                            objList.Add(o);
                DisplayMemory();
            }

            GC.Collect();


            DisplayMemory();
            Console.WriteLine("--- press any key to quit ---");

            Console.ReadLine();
        }

Наблюдайте, каждый раз, когда объект создается, общая память увеличивается на 120 байт.