Поделиться через


Сборка мусора и производительность

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

Средства анализа производительности

В следующих разделах описываются средства, которые можно использовать для диагностики проблем с использованием памяти и сборкой мусора. Описанные далее в этой статье процедуры относятся к этим средствам.

Счетчики производительности памяти

Счетчики производительности можно использовать для сбора данных производительности. Инструкции см. в разделе Профилирование среды выполнения. Категория счетчиков производительности "Память CLR .NET" (как описано в разделе Счетчики производительности в .NET) предоставляет сведения о сборщике мусора.

Отладка с помощью расширения SOS

Для проверки объектов в управляемой куче можно использовать отладчик Windows (WinDbg).

Чтобы установить WinDbg, установите средства отладки для Windows со страницы скачивания средств отладки для Windows.

События сборки мусора (трассировка событий Windows)

Трассировка событий Windows (ETW) — это система трассировки, дополняющая поддержку профилирования и отладки в .NET. Начиная с .NET Framework 4, события ETW для сборки мусора собирают полезные сведения для анализа управляемой кучи с точки зрения статистики. Например, событие GCStart_V1, которое вызывается перед началом сборки мусора, предоставляет следующие сведения:

  • собираемое поколение объектов;
  • что активировало сборку мусора;
  • тип сборки мусора (параллельная или непараллельная).

Ведение журнала событий трассировки событий Windows эффективно и не скрывает проблемы производительности, связанные со сборкой мусора. Процесс может предоставлять свои собственные события вместе с событиями трассировки событий Windows. При регистрации в журнале события приложения можно соотнести с событиями сборки мусора, чтобы определить, как и когда возникают проблемы кучи. Например, серверное приложение может предоставлять события в начале и в конце запроса клиента.

API профилирования

Интерфейсы профилирования среды выполнения (CLR) предоставляют подробные сведения об объектах, которые были затронуты во время сборки мусора. Профилировщик может получать уведомления о начале и завершении сборки мусора. Он может предоставлять отчеты об объектах в управляемой куче, включая идентификацию объектов в каждом поколении. Дополнительные сведения см. в разделе Общие сведения о профилировании.

Профилировщики могут предоставлять исчерпывающую информацию. Однако сложные профилировщики потенциально могут менять поведение приложения.

Отслеживание ресурсов домена приложения

Начиная с .NET Framework 4, функция отслеживания ресурсов домена приложения (ARM) позволяет узлам отслеживать загрузку ЦП и использование памяти доменом приложения. Дополнительные сведения см. в разделе Отслеживание ресурсов домена приложения.

Устранение проблем с производительностью

Первый этап — определить, действительно ли проблема относится к сборке мусора. Если это так, выберите одну из проблем в списке ниже, чтобы определить способ ее устранения.

Проблема: создается исключение нехватки памяти

Существует два допустимых случая создания управляемого исключения OutOfMemoryException:

  • нехватка виртуальной памяти;

    Сборщик мусора распределяет память из системы в виде сегментов заранее заданного размера. Если выделению требуется дополнительный сегмент, но в пространстве виртуальной памяти процесса не осталось непрерывных свободных блоков, произойдет сбой выделения памяти для управляемой кучи.

  • нехватка физической памяти для выделения.

Проверки производительности
Определите, является ли исключение нехватки памяти управляемым.
Определите, сколько виртуальной памяти можно зарезервировать.
Определите, имеется ли достаточный объем физической памяти.

Если выяснилось, что исключение не относится к допустимым сценариям, обратитесь в службу технической поддержки Майкрософт, сообщив следующие сведения:

  • стек с управляемым исключением нехватки памяти,
  • полный дамп памяти,
  • данные, подтверждающие недопустимый характер исключения нехватки памяти, включая данные, показывающие, что проблема не связана с виртуальной или физической памятью.

Проблема: процесс использует слишком много памяти

Считается, что с помощью индикатора использования памяти на вкладке Быстродействие диспетчера задач Windows можно определить, что процесс использует слишком много памяти. Однако этот индикатор относится к рабочему набору; он не предоставляет сведения об использовании виртуальной памяти.

Если выяснилось, что проблема вызвана управляемой кучей, необходимо провести для управляемой кучи измерения в динамике, чтобы определить наличие каких-либо шаблонов.

Если выяснилось, что проблема не вызвана управляемой кучей, необходимо использовать отладку неуправляемого кода.

Проверки производительности
Определите, сколько виртуальной памяти можно зарезервировать.
Определите объем памяти, зафиксированный управляемой кучей.
Определите объем памяти, зарезервированный управляемой кучей.
Определите большие объекты в поколении 2.
Определите ссылки на объекты.

Проблема: сборщик мусора недостаточно быстро освобождает объекты

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

Эта проблема также может возникать, если не выполнялась сборка мусора для поколения, содержащего неиспользуемый объект. Это является признаком того, что для неиспользуемого объекта не выполнялся метод завершения. Например, это может происходить, когда вы запускаете приложение однопотокового подразделения (STA) и не удается вызвать в него поток, который обслуживает очередь метода завершения.

Проверки производительности
Проверьте ссылки на объекты.
Определите, выполнялся ли метод завершения.
Определите наличие объектов, ожидающих завершения.

Проблема: управляемая куча слишком сильно фрагментирована

Уровень фрагментации рассчитывается как отношение свободного пространства ко всей выделенной памяти для поколения. Для поколения 2 допустимый уровень фрагментации составляет не более 20 %. Поскольку поколение 2 может очень сильно вырасти, показатель фрагментации более важен, чем абсолютное значение.

Наличие большого объема свободного пространства в поколении 0 не является проблемой, так как это поколение, в котором выделяются новые объекты.

Фрагментация всегда возникает в куче больших объектов, так как для нее не выполняется сжатие. Смежные свободные объекты естественным образом свертываются в одно пространство для удовлетворения запросов на размещение больших объектов.

Фрагментация может стать проблемой в поколении 1 и 2. Если эти поколения имеют большой объем свободного места после сборки мусора, возможно, потребуется внести изменения в использование объекта приложения. Кроме того, может потребоваться пересмотреть время существования долгосрочных объектов.

Чрезмерное закрепление объектов может привести к увеличению фрагментации. Если наблюдается высокая фрагментация, возможно, закреплено слишком много объектов.

Если фрагментация виртуальной памяти препятствует добавлению сегментов сборщиком мусора, возможны следующие причины:

  • частая загрузка и выгрузка большого числа небольших сборок;

  • поддержание слишком большого числа ссылок на COM-объекты при взаимодействии с неуправляемым кодом;

  • создание больших временных объектов, что приводит к частому выделению и освобождению сегментов кучи больших объектов.

    При размещении среды CLR приложение может запросить сохранение сегментов сборщиком мусора. Это снижает частоту выделения сегментов. Для этого необходимо использовать флаг STARTUP_HOARD_GC_VM в перечислении STARTUP_FLAGS.

Проверки производительности
Определите объем свободного пространства в управляемой куче.
Определите количество закрепленных объектов.

Если вы считаете, что нет допустимых причин для фрагментации, обратитесь в службу технической поддержки Майкрософт.

Проблема: слишком большие интервалы между сборками мусора

Сборка мусора выполняется в режиме мягкого реального времени, поэтому приложение должно быть способно допускать определенные перерывы. Критерием мягкого реального времени является своевременное завершение 95 % операций.

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

Эфемерные сборки мусора (поколения 0 и 1) длятся лишь несколько миллисекунд, поэтому уменьшение перерывов обычно нецелесообразно. Тем не менее, перерывы в сборках поколения 2 можно сократить, изменив шаблон обработки запросов приложения на выделение памяти.

Еще одним, более точным методом является использование событий трассировки ETW для сборки мусора. Затраты времени для сборок можно найти путем добавления разницы отметок времени для последовательности событий. Вся последовательность сборки включает приостановку подсистемы выполнения, собственно сборку мусора и возобновление работы подсистемы выполнения.

С помощью уведомлений сборки мусора можно определить, когда сервер приступает к сборке поколения 2. Кроме того, с их помощью можно определить, поможет ли маршрутизация запросов на другой сервер решить проблемы, связанные с задержками.

Проверки производительности
Определите продолжительность сборки мусора.
Определите, что вызвало сборку мусора.

Проблема: слишком большой размер поколения 0

Поколение 0 скорее всего имеет большое количество объектов в 64-разрядной системе, особенно если используется сборка мусора на сервере вместо сборки мусора на рабочей станции. Это происходит потому, что пороговое значение запуска сборки мусора для поколения 0 в таких средах выше и объемы сборки поколения 0 могут значительно увеличиваться. Производительность повышается, если приложение выделяет больше памяти перед активацией сборки мусора.

Проблема: слишком высокая загрузка ЦП во время сборки мусора

Во время сборки мусора загрузка ЦП будет высокой, если на сборку мусора тратится значительное время обработки, количество сборок слишком велико или сборка продолжается слишком долго. Чем чаще выполняется выделение памяти для объектов в управляемой куче, тем чаще происходит сборка мусора. Уменьшение частоты выделения снижает частоту сборки мусора.

Частоту выделения можно отслеживать с помощью счетчика производительности Allocated Bytes/second. Дополнительные сведения см. в статье Счетчики производительности в .NET.

Длительность сборки определяется в первую очередь количеством объектов, оставшихся после выделения. Сборщик мусора должен пройти большой объем памяти, если требуется выполнить сборку многих объектов. Работа по сжатию оставшихся объектов занимает много времени. Чтобы определить, сколько объектов было обработано во время сборки, установите точку останова в отладчике в конце сборки мусора для заданного поколения.

Проверки производительности
Определите, вызвана ли высокая загрузка ЦП сборкой мусора.
Задайте точку останова в конце сборки мусора.

Рекомендации по устранению неполадок

В этом разделе приводятся рекомендации, на которые следует опираться при проведении анализа.

Сборка мусора на сервере или рабочей станции

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

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

Когда измерять размер управляемой кучи

Если вы не используете профилировщик, необходимо создать согласованный шаблон измерений для эффективной диагностики проблем с производительностью. При формировании расписания учтите следующие моменты.

  • Если измерение выполняется после сборки мусора поколения 2, вся куча будет свободна от мусора (неиспользуемых объектов).
  • Если измерение выполняется сразу после сборки мусора поколения 0, объекты в поколении 1 и 2 еще не будут собраны.
  • Если измерение выполняется сразу же перед сборкой мусора, будет измерено максимально возможное выделение до начала сборки мусора.
  • Измерение во время сборки мусора проблематично, поскольку структуры данных сборщика мусора не находятся в допустимом состоянии для обхода и могут не предоставлять полные результаты. Это сделано намеренно.
  • При использовании сборки мусора на рабочей станции с параллельной сборкой мусора освобожденные объекты не сжимаются, поэтому размер кучи может быть таким же или больше (из-за фрагментации размер может казаться больше).
  • Параллельная сборка мусора для поколения 2 откладывается, если загрузка физической памяти слишком велика.

Ниже приведена процедура задания точки останова для измерения управляемой кучи.

Задание точки останова в конце сборки мусора

  • В WinDbg с загруженным расширением отладчика SOS введите следующую команду:

    bp mscorwks!WKS::GCHeap::RestartEE "j (dwo(mscorwks!WKS::GCHeap::GcCondemnedGeneration)==2) 'kb';'g'"

    Задайте GcCondemnedGeneration требуемое поколение. Команда требует закрытых символов.

    Эта команда вызывает разрыв, если RestartEE выполняется после восстановления объектов поколения 2 для сборки мусора.

    В сборке мусора сервера вызывается только один поток RestartEE, поэтому точка останова будет происходить только один раз во время сборки мусора поколения 2.

Процедуры проверка производительности

В этом разделе описаны следующие процедуры по выявлению причин проблем с производительностью.

Чтобы определить, вызвана ли проблема сборкой мусора, выполните следующие действия.

  • Проверьте следующие два счетчика производительности памяти:

    • % времени в сборке мусора. Отображение времени, потраченного на выполнение сборки мусора с момента последнего цикла сборки мусора (в процентах). Этот счетчик используется, чтобы определить, тратит ли сборщик мусора слишком много времени на освобождение пространства в управляемой куче. Если время, затраченное на сборку мусора, сравнительно мало, это может указывать на проблему ресурсов за пределами управляемой кучи. Этот счетчик может предоставлять неточные данные при участии параллельной или фоновой сборки мусора.

    • Всего зафиксировано байт. Отображение объема виртуальной памяти, в данный момент выделенной сборщиком мусора. С помощью этого счетчика можно определить, является ли память, используемая сборщиком мусора, избыточной частью памяти, которую использует приложение.

    Большинство счетчиков памяти обновляются в конце каждой сборки мусора. Таким образом, они могут не отражать текущие условия, сведения о которых вам требуются.

Чтобы определить, является ли исключение нехватки памяти управляемым, выполните следующие действия.

  1. В отладчике WinDbg или Visual Studio с расширением отладчика SOS, загруженном, введите команду исключения печати (pe) :

    !pe

    Если исключение является управляемым, OutOfMemoryException отображается как тип исключения, как показано в следующем примере.

    Exception object: 39594518
    Exception type: System.OutOfMemoryException
    Message: <none>
    InnerException: <none>
    StackTrace (generated):
    
  2. Если выходные данные не указывают на исключение, необходимо определить, к какому потоку относится исключение нехватки памяти. Введите следующую команду в отладчике, чтобы отобразить все потоки со своими стеками вызовов:

    ~\*kb

    Поток со стеком, для которого есть вызовы исключений, обозначается аргументом RaiseTheException. Это объект управляемого исключения.

    28adfb44 7923918f 5b61f2b4 00000000 5b61f2b4 mscorwks!RaiseTheException+0xa0
    
  3. Чтобы создать дамп вложенных исключений, можно использовать следующую команду.

    !pe -nested

    Если исключения не обнаружены, значит исключение нехватки памяти возникло в неуправляемом коде.

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

  • В WinDbg с загруженным расширением отладчика SOS введите следующую команду, чтобы получить самый большой бесплатный регион:

    !address -summary

    Самая крупная свободная область отображается, как показано в выходных данных команды ниже.

    Largest free region: Base 54000000 - Size 0003A980
    

    В этом примере размер самой крупной свободной области составляет приблизительно 24 000 КБ (3A980 в шестнадцатеричном формате). Эта область гораздо меньше, чем требуется сборщику мусора для сегмента.

    –или–

  • Используйте команду vmstat:

    !vmstat

    Самая крупная свободная область представлена самым большим значением в столбце MAXIMUM, как показано в следующих выходных данных.

    TYPE        MINIMUM   MAXIMUM     AVERAGE   BLK COUNT   TOTAL
    ~~~~        ~~~~~~~   ~~~~~~~     ~~~~~~~   ~~~~~~~~~~  ~~~~
    Free:
    Small       8K        64K         46K       36          1,671K
    Medium      80K       864K        349K      3           1,047K
    Large       1,384K    1,278,848K  151,834K  12          1,822,015K
    Summary     8K        1,278,848K  35,779K   51          1,824,735K
    

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

  1. Запустите диспетчер задач Windows.

  2. На вкладке просмотрите Performance зафиксированное значение. (В Windows 7 посмотрите на Commit (KB)System group.)

    Если он Total близок к Limit, вы работаете с низкой физической памятью.

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

  • Используйте счетчик производительности памяти # Total committed bytes, чтобы получить число байтов, фиксируемых управляемой кучей. Сборщик мусора выделяет фрагменты в сегменте по мере необходимости, но не все одновременно.

    Примечание.

    Не используйте счетчик производительности # Bytes in all Heaps, так как он не отражает фактическое использование памяти управляемой кучей. Размер поколения включается в это значение и, фактически, является его пороговым размером, то есть размером, который вызывает сборку мусора, если поколение заполнено объектами. Поэтому это значение обычно равно нулю.

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

  • Используйте счетчик производительности памяти # Total reserved bytes.

    Сборщик мусора резервирует память в сегментах, и вы можете определить, где начинается сегмент с помощью eeheap команды.

    Внимание

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

  • В отладчике WinDbg или Visual Studio с загруженным расширением отладчика SOS введите следующую команду:

    !eeheap -gc

    Результат выглядит следующим образом.

    Number of GC Heaps: 2
    ------------------------------
    Heap 0 (002db550)
    generation 0 starts at 0x02abe29c
    generation 1 starts at 0x02abdd08
    generation 2 starts at 0x02ab0038
    ephemeral segment allocation context: none
      segment    begin allocated     size
    02ab0000 02ab0038  02aceff4 0x0001efbc(126908)
    Large object heap starts at 0x0aab0038
      segment    begin allocated     size
    0aab0000 0aab0038  0aab2278 0x00002240(8768)
    Heap Size   0x211fc(135676)
    ------------------------------
    Heap 1 (002dc958)
    generation 0 starts at 0x06ab1bd8
    generation 1 starts at 0x06ab1bcc
    generation 2 starts at 0x06ab0038
    ephemeral segment allocation context: none
      segment    begin allocated     size
    06ab0000 06ab0038  06ab3be4 0x00003bac(15276)
    Large object heap starts at 0x0cab0038
      segment    begin allocated     size
    0cab0000 0cab0038  0cab0048 0x00000010(16)
    Heap Size    0x3bbc(15292)
    ------------------------------
    GC Heap Size   0x24db8(150968)
    

    Адреса с отметкой segment являются начальными адресами сегментов.

Чтобы определить большие объекты в поколении 2, выполните следующие действия.

  • В отладчике WinDbg или Visual Studio с загруженным расширением отладчика SOS введите следующую команду:

    !dumpheap –stat

    Если управляемая куча большая, dumpheap может занять некоторое время.

    Вы можете начать анализ с последних нескольких строк вывода, поскольку в них перечислены объекты, занимающие наибольшее пространство. Например:

    2c6108d4   173712     14591808 DevExpress.XtraGrid.Views.Grid.ViewInfo.GridCellInfo
    00155f80      533     15216804      Free
    7a747c78   791070     15821400 System.Collections.Specialized.ListDictionary+DictionaryNode
    7a747bac   700930     19626040 System.Collections.Specialized.ListDictionary
    2c64e36c    78644     20762016 DevExpress.XtraEditors.ViewInfo.TextEditViewInfo
    79124228   121143     29064120 System.Object[]
    035f0ee4    81626     35588936 Toolkit.TlkOrder
    00fcae40     6193     44911636 WaveBasedStrategy.Tick_Snap[]
    791242ec    40182     90664128 System.Collections.Hashtable+bucket[]
    790fa3e0  3154024    137881448 System.String
    Total 8454945 objects
    

    Последний объект в списке является строкой и занимает больше всего места. Вы можете проанализировать приложение, чтобы понять, как можно оптимизировать строковые объекты. Чтобы просмотреть строки в диапазоне от 150 до 200 байт, введите следующее:

    !dumpheap -type System.String -min 150 -max 200

    Далее приведен пример результатов команды.

    Address  MT           Size  Gen
    1875d2c0 790fa3e0      152    2 System.String HighlightNullStyle_Blotter_PendingOrder-11_Blotter_PendingOrder-11
    …
    

    Использование целого числа вместо строки для идентификаторов может быть более эффективным. Если та же строка повторяется тысячи раз, рекомендуется настроить интернирование строк. Дополнительные сведения об интернировании строк см. в разделе справки по методу String.Intern.

Чтобы определить ссылки на объекты, выполните следующие действия.

  • В WinDbg с загруженным расширением отладчика SOS введите следующую команду, чтобы получить список ссылок на объекты:

    !gcroot

    –или–

  • Чтобы определить ссылки для конкретного объекта, включите соответствующий адрес:

    !gcroot 1c37b2ac

    Корни, найденные в стеках, могут быть ложными положительными результатами. Чтобы получить дополнительные сведения, используйте команду !help gcroot.

    ebx:Root:19011c5c(System.Windows.Forms.Application+ThreadContext)->
    19010b78(DemoApp.FormDemoApp)->
    19011158(System.Windows.Forms.PropertyStore)->
    … [omitted]
    1c3745ec(System.Data.DataTable)->
    1c3747a8(System.Data.DataColumnCollection)->
    1c3747f8(System.Collections.Hashtable)->
    1c376590(System.Collections.Hashtable+bucket[])->
    1c376c98(System.Data.DataColumn)->
    1c37b270(System.Data.Common.DoubleStorage)->
    1c37b2ac(System.Double[])
    Scan Thread 0 OSTHread 99c
    Scan Thread 6 OSTHread 484
    

    Команда gcroot может занять много времени. Любой объект, не освобождаемый сборщиком мусора, является активным объектом. Это означает, что некоторый корневой каталог напрямую или косвенно держится на объекте, поэтому gcroot должен возвращать сведения о пути к объекту. Необходимо изучить возвращенные диаграммы, чтобы понять, на какие объекты по-прежнему имеются ссылки.

Чтобы определить, выполнялся ли метод завершения, выполните следующие действия.

  • Запустите тестовую программу, содержащую следующий код:

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

    Если с помощью теста удалось разрешить проблему, значит сборщик мусора не освобождает объекты, так как методы завершения для этих объектов приостановлены. Метод GC.WaitForPendingFinalizers позволяет методам завершения выполнить свои задачи и устраняет проблему.

Чтобы определить наличие объектов, ожидающих завершения, выполните следующие действия.

  1. В отладчике WinDbg или Visual Studio с загруженным расширением отладчика SOS введите следующую команду:

    !finalizequeue

    Проверьте число объектов, которые готовы к завершению. Если число большое, необходимо выяснить, почему эти методы завершения вообще не могут выполняться или не могут выполняться достаточно быстро.

  2. Чтобы получить выходные данные потоков, введите следующую команду:

    !threads -special

    Эта команда предоставляет примерно следующие выходные данные.

       OSID     Special thread type
    2    cd0    DbgHelper
    3    c18    Finalizer
    4    df0    GC SuspendEE
    

    Поток метода завершения указывает, какой метод завершения (при наличии) выполняется в настоящее время. Если в потоке метода завершения не выполняются никакие методы завершения, значит он ожидает событие, которое запустит его выполнение. Как правило, поток находится в этом состоянии, так как выполняется с приоритетом THREAD_HIGHEST_PRIORITY и должен закончить выполнение методов завершения (при наличии) очень быстро.

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

  • В отладчике WinDbg или Visual Studio с загруженным расширением отладчика SOS введите следующую команду:

    !dumpheap -type Free -stat

    Эта команда выводит на экран общий размер всех свободных объектов в управляемой куче, как показано в следующем примере.

    total 230 objects
    Statistics:
          MT    Count    TotalSize Class Name
    00152b18      230     40958584      Free
    Total 230 objects
    
  • Чтобы определить свободное пространство в поколении 0, введите следующую команду для сведений о потреблении памяти по поколению:

    !eeheap -gc

    Эта команда выводит примерно следующие сведения. В последней строке показан эфемерный сегмент.

    Heap 0 (0015ad08)
    generation 0 starts at 0x49521f8c
    generation 1 starts at 0x494d7f64
    generation 2 starts at 0x007f0038
    ephemeral segment allocation context: none
    segment  begin     allocated  size
    00178250 7a80d84c  7a82f1cc   0x00021980(137600)
    00161918 78c50e40  78c7056c   0x0001f72c(128812)
    007f0000 007f0038  047eed28   0x03ffecf0(67103984)
    3a120000 3a120038  3a3e84f8   0x002c84c0(2917568)
    46120000 46120038  49e05d04   0x03ce5ccc(63855820)
    
  • Вычислите пространство, используемое поколением 0:

    ? 49e05d04-0x49521f8c

    Результат выглядит следующим образом. Поколение 0 занимает приблизительно 9 МБ.

    Evaluate expression: 9321848 = 008e3d78
    
  • Следующая команда создает дамп свободного места в диапазоне поколения 0:

    !dumpheap -type Free -stat 0x49521f8c 49e05d04

    Результат выглядит следующим образом.

    ------------------------------
    Heap 0
    total 409 objects
    ------------------------------
    Heap 1
    total 0 objects
    ------------------------------
    Heap 2
    total 0 objects
    ------------------------------
    Heap 3
    total 0 objects
    ------------------------------
    total 409 objects
    Statistics:
          MT    Count TotalSize Class Name
    0015a498      409   7296540      Free
    Total 409 objects
    

    Эти выходные данные показывают, что часть кучи поколения 0 использует 9 МБ пространства для объектов, а 7 МБ свободны. Этот анализ показывает предел, до которого поколение 0 участвует в фрагментации. Этот объем используемой памяти кучи необходимо вычесть из общего объема как причину фрагментации долгосрочными объектами.

Чтобы определить количество закрепленных объектов, выполните следующие действия.

  • В отладчике WinDbg или Visual Studio с загруженным расширением отладчика SOS введите следующую команду:

    !gchandles

    Выводимая на экран статистика включает число закрепленных дескрипторов, как показано в следующем примере.

    GC Handle Statistics:
    Strong Handles:      29
    Pinned Handles:      10
    

Чтобы определить продолжительность сборки мусора, выполните следующие действия.

  • Проверьте счетчик производительности памяти % Time in GC.

    Значение вычисляется с помощью интервала выборки. Поскольку счетчики обновляются в конце каждой сборки мусора, текущая выборка будет иметь то же значение, что и предыдущая, если в течение интервала не будут выполняться сборки мусора.

    Время сборки вычисляется путем умножения интервала выборки на процентное значение.

    В следующих данных показаны четыре интервала выборки продолжительностью две секунды для 8-секундного исследования. Gen1Столбцы Gen0и Gen2 столбцы показывают общее количество сборок мусора, завершенных к концу интервала для этого поколения.

    Interval    Gen0    Gen1    Gen2    % Time in GC
            1       9       3       1              10
            2      10       3       1               1
            3      11       3       1               3
            4      11       3       1               3
    

    Эти сведения не указывают на момент выполнения сборки мусора, но вы можете определить число сборок, выполнявшихся в определенный интервал. Предположим, что худший случай, десятый поколение 0 сборки мусора закончилось в начале второго интервала, и одиннадцатое поколение 0 сборки мусора закончилось в конце третьего интервала. Время между окончанием десятой и одиннадцатой сборок мусора составляет 2 секунды, а счетчик производительности выдает значение 3 %, таким образом длительность одиннадцатой сборки мусора поколения 0 составляет (2 секунды * 3 % =) 60 мс.

    В следующем примере существует пять интервалов.

    Interval    Gen0    Gen1    Gen2     % Time in GC
            1       9       3       1                3
            2      10       3       1                1
            3      11       4       1                1
            4      11       4       1                1
            5      11       4       2               20
    

    Второе поколение 2 сборка мусора началась во время четвертого интервала и завершилась на пятом интервале. Предполагая худший случай, последняя сборка мусора была для коллекции поколения 0, которая закончилась в начале третьего интервала, и сборка мусора поколения 2 закончилась в конце пятого интервала. Таким образом, время между завершением сборки мусора для поколения 0 и завершением сборки мусора для поколения 2 составляет 4 секунды. Поскольку счетчик % Time in GC выдает значение 20 %, максимальная длительность сборки мусора поколения 2 могла составлять (4 секунды * 20 % =) 800 мс.

  • Кроме того, продолжительность сборки мусора можно определить с помощью событий ETW для сборки мусора, проанализировав данные для выяснения длительности сборки мусора.

    Например, следующие данные показывают последовательность событий, возникших во время непараллельной сборки мусора.

    Timestamp    Event name
    513052        GCSuspendEEBegin_V1
    513078        GCSuspendEEEnd
    513090        GCStart_V1
    517890        GCEnd_V1
    517894        GCHeapStats
    517897        GCRestartEEBegin
    517918        GCRestartEEEnd
    

    Приостановка управляемого потока заняла 26 мкс (GCSuspendEEEndGCSuspendEEBegin_V1).

    Фактическая сборка мусора заняла 4,8 мс (GCEnd_V1GCStart_V1).

    Возобновление управляемого потока заняло 21 мкс (GCRestartEEEndGCRestartEEBegin).

    Следующие выходные данные содержат пример фоновой сборки мусора и включают сведения о процессе, потоке и полях событий (показаны не все данные).

    timestamp(us)    event name            process    thread    event field
    42504385        GCSuspendEEBegin_V1    Test.exe    4372             1
    42504648        GCSuspendEEEnd         Test.exe    4372
    42504816        GCStart_V1             Test.exe    4372        102019
    42504907        GCStart_V1             Test.exe    4372        102020
    42514170        GCEnd_V1               Test.exe    4372
    42514204        GCHeapStats            Test.exe    4372        102020
    42832052        GCRestartEEBegin       Test.exe    4372
    42832136        GCRestartEEEnd         Test.exe    4372
    63685394        GCSuspendEEBegin_V1    Test.exe    4744             6
    63686347        GCSuspendEEEnd         Test.exe    4744
    63784294        GCRestartEEBegin       Test.exe    4744
    63784407        GCRestartEEEnd         Test.exe    4744
    89931423        GCEnd_V1               Test.exe    4372        102019
    89931464        GCHeapStats            Test.exe    4372
    

    Событие GCStart_V1 на отметке 42504816 указывает на выполнение фоновой сборки мусора, так как последнее поле — 1. Это становится сборкой мусора No. 102019.

    Событие GCStart возникает из-за необходимости выполнить эфемерную сборку мусора до запуска фоновой сборки мусора. Это становится сборкой мусора No 102020.

    На 42514170 сборка мусора No 102020 завершается. На этом этапе перезапускаются управляемые потоки. Это действие выполняется для потока 4372, активировавшего эту фоновую сборку мусора.

    Для потока 4744 происходит приостановка. Это единственный случай, когда фоновой сборке мусора приходится приостанавливать управляемые потоки. Продолжительность приостановки составляет примерно 99 мс ((63784407-63685394)/1000).

    Событие GCEnd для фоновой сборки мусора находится на отметке 89931423. Это означает, что фоновая сборка мусора продолжалась около 47 секунд ((89931423-42504816)/1000).

    Во время выполнения управляемых потоков можно наблюдать любое количество эфемерных сборок мусора.

Чтобы определить, что активировало сборку мусора, выполните следующие действия.

  • В отладчике WinDbg или Visual Studio с загруженным расширением отладчика SOS введите следующую команду, чтобы отобразить все потоки со стеками вызовов:

    ~*Кб

    Эта команда выводит примерно следующие сведения.

    0012f3b0 79ff0bf8 mscorwks!WKS::GCHeap::GarbageCollect
    0012f454 30002894 mscorwks!GCInterface::CollectGeneration+0xa4
    0012f490 79fa22bd fragment_ni!request.Main(System.String[])+0x48
    

    Если сборка мусора была вызвана уведомлением о нехватке памяти от операционной системы, стек вызовов аналогичен за исключением того, что поток является потоком метода завершения. Поток метода завершения получает асинхронное уведомление о нехватке памяти и запускает сборку мусора.

    Если сборка мусора вызвана выделением памяти, стек будет следующим:

    0012f230 7a07c551 mscorwks!WKS::GCHeap::GarbageCollectGeneration
    0012f2b8 7a07cba8 mscorwks!WKS::gc_heap::try_allocate_more_space+0x1a1
    0012f2d4 7a07cefb mscorwks!WKS::gc_heap::allocate_more_space+0x18
    0012f2f4 7a02a51b mscorwks!WKS::GCHeap::Alloc+0x4b
    0012f310 7a02ae4c mscorwks!Alloc+0x60
    0012f364 7a030e46 mscorwks!FastAllocatePrimitiveArray+0xbd
    0012f424 300027f4 mscorwks!JIT_NewArr1+0x148
    000af70f 3000299f fragment_ni!request..ctor(Int32, Single)+0x20c
    0000002a 79fa22bd fragment_ni!request.Main(System.String[])+0x153
    

    Вспомогательный JIT-объект (JIT_New*) в конечном итоге вызывает GCHeap::GarbageCollectGeneration. Если выяснилось, что сборки мусора поколения 2 вызываются выделением памяти, необходимо определить, какие объекты собираются при сборке мусора поколения 2, и постараться избежать их. То есть необходимо определить разницу между началом и концом сборки мусора поколения 2 и выяснить, какие объекты, вызвали сборку мусора поколения 2.

    Например, введите следующую команду в отладчике, чтобы отобразить начало коллекции поколения 2:

    !dumpheap –stat

    Пример выходных данных (сокращено для отображения объектов, использующих наибольшее пространство):

    79124228    31857      9862328 System.Object[]
    035f0384    25668     11601936 Toolkit.TlkPosition
    00155f80    21248     12256296      Free
    79103b6c   297003     13068132 System.Threading.ReaderWriterLock
    7a747ad4   708732     14174640 System.Collections.Specialized.HybridDictionary
    7a747c78   786498     15729960 System.Collections.Specialized.ListDictionary+DictionaryNode
    7a747bac   700298     19608344 System.Collections.Specialized.ListDictionary
    035f0ee4    89192     38887712 Toolkit.TlkOrder
    00fcae40     6193     44911636 WaveBasedStrategy.Tick_Snap[]
    7912c444    91616     71887080 System.Double[]
    791242ec    32451     82462728 System.Collections.Hashtable+bucket[]
    790fa3e0  2459154    112128436 System.String
    Total 6471774 objects
    

    Повторите команду в конце сборки поколения 2:

    !dumpheap –stat

    Пример выходных данных (сокращено для отображения объектов, использующих наибольшее пространство):

    79124228    26648      9314256 System.Object[]
    035f0384    25668     11601936 Toolkit.TlkPosition
    79103b6c   296770     13057880 System.Threading.ReaderWriterLock
    7a747ad4   708730     14174600 System.Collections.Specialized.HybridDictionary
    7a747c78   786497     15729940 System.Collections.Specialized.ListDictionary+DictionaryNode
    7a747bac   700298     19608344 System.Collections.Specialized.ListDictionary
    00155f80    13806     34007212      Free
    035f0ee4    89187     38885532 Toolkit.TlkOrder
    00fcae40     6193     44911636 WaveBasedStrategy.Tick_Snap[]
    791242ec    32370     82359768 System.Collections.Hashtable+bucket[]
    790fa3e0  2440020    111341808 System.String
    Total 6417525 objects
    

    Объекты double[] больше не отображаются в конце списка выходных данных, что означает, что они были собраны. На эти объекты приходится приблизительно 70 МБ. Остальные объекты не сильно изменились. Следовательно, эти объекты double[] были причиной выполнения этой сборки мусора поколения 2. Затем вам потребуется выяснить, почему объекты double[] попали в сборку и почему они перестали использоваться. Вы можете попросить разработчика кода, откуда пришли эти объекты, или использовать gcroot команду.

Чтобы определить, вызвана ли высокая загрузка ЦП сборкой мусора, выполните следующие действия.

  • Сопоставьте значение счетчика производительности памяти % Time in GC с временем обработки.

    Если значение % Time in GC резко возрастает одновременно с временем обработки, значит сборка мусора вызывает высокую загрузку ЦП. В противном случае выполните профилирование приложения, чтобы определить, где возникает высокая загрузка.

См. также