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


Отслеживание объектов в интерфейсе API для профилирования

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

Перемещение объектов

При перемещении объектов значения ObjectID, назначенные предыдущими уведомлениями, меняются. Внутреннее состояние самого объекта остается неизменным за исключением его ссылок на другие объекты. Изменению подвергается только его расположение в памяти (и, как следствие, его идентификатор ObjectID). Уведомление ICorProfilerCallback::MovedReferences позволяет профилировщику обновить внутренние таблицы, отслеживающие сведения по ObjectID. Имя метода MovedReferences может ввести в заблуждение, поскольку оно используется даже для объектов, которые не перемещались.

Количество объектов в куче может насчитывать тысячи или миллионы. Следовательно, отслеживать перемещение такого большого числа объектов, задавая для каждого из них начальный и конечный идентификатор, не практично. Поэтому сборщик мусора пытается перемещать соседние активные объекты блоками, чтобы они оставались соседями и в своем новом расположении в куче. Уведомление MovedReferences сообщает начальное и конечное значение идентификаторов ObjectID этих блоков соседних объектов.

Предположим, что существующее значение ObjectID (oldObjectID) принадлежит следующему диапазону:

oldObjectIDRangeStart[i] <= oldObjectID < oldObjectIDRangeStart[i] + cObjectIDRangeLength[i]

В этом случае смещение от начала диапазона к началу объекта будет следующим:

oldObjectID - oldObjectRangeStart[i]

Для любого значения параметра i принадлежащего следующему диапазону:

0 <= i < cMovedObjectIDRanges

новое значение ObjectID можно вычислить по следующей формуле:

newObjectID = newObjectIDRangeStart[i] + (oldObjectID – oldObjectIDRangeStart[i])

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

На следующей иллюстрации показаны 10 объектов до выполнения сборки мусора. Их начальные адреса (эквивалентны идентификаторам ObjectID) равны 08, 09, 10, 12, 13, 15, 16, 17, 18 и 19. Объекты с идентификаторами ObjectID 09, 13 и 19 не используются, поэтому занимаемое ими место будет освобождено во время сборки мусора.

Перемещение объекта в процессе сборки мусора

Перемещение объекта в процессе сборки мусора

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

Объект

oldObjectIDRangeStart[]

newObjectIDRangeStart[]

0

08

07

1

09

2

10

08

3

12

10

4

13

5

15

11

6

16

12

7

17

13

8

18

14

9

19

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

Блоки

oldObjectIDRangeStart[]

newObjectIDRangeStart[]

cObjectIDRangeLength[]

0

08

07

1

1

10

08

2

2

15

11

4

Выявление всех удаленных объектов

Метод MovedReferences сообщает обо всех объектах, которые остались после сборки мусора со сжатием, независимо от того, перемещались они или нет. Ни один из объектов, о которых не сообщил метод MovedReferences, не сохранились. Однако не всякая сборка мусора выполняется со сжатием. В платформе .NET Framework версии 1.0 и 1.1 профилировщик не мог обнаружить объекты, сохранившиеся при сборке мусора без сжатия (сборке мусора, вовремя которой перемещение объектов не выполняется). В платформе .NET Framework версии 2.0 такой сценарий получил лучшую поддержку благодаря следующим двум методам.

  • Вызвав метод ICorProfilerInfo2::GetGenerationBounds, профилировщик может получить границы сегментов кучи для сборки мусора. С помощью поля rangeLength в результирующей структуре COR_PRF_GC_GENERATION_RANGE можно определить протяженность активных объектов в сжимаемой области.

  • Обратный вызов ICorProfilerCallback2::GarbageCollectionStarted указывает, какие поколения подбираются в ходе текущей сборки мусора. Все не подобранные объекты в поколении после сборки мусора сохранятся.

  • Обратный вызов ICorProfilerCallback2::SurvivingReferences указывает объекты, сохранившиеся после сборки мусора без сжатия.

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

Примечания

После сборки мусора выполнение приложения останавливается до тех пор, пока среда выполнения не завершит передачу информации о куче профилировщику кода. С помощью метода ICorProfilerInfo::GetClassFromObject можно получить идентификатор класса объекта ClassID. Методы ICorProfilerInfo::GetClassIDInfo или ICorProfilerInfo2::GetClassIDInfo2 можно использовать для получения сведений о метаданных, связанных с данным классом.

В платформе .NET Framework версии 1.0 и 1.1, после завершения сборки мусора каждый сохранившийся объект должен быть корневой ссылкой, обладающей родительским объектом, являющимся корневой ссылкой, или находится в поколении, которое не было подобрано. Иногда могут возникать объекты, не принадлежащие ни к одной из этих категорий. Эти объекты могут распределяться внутренне средой выполнения либо представлять собой слабые ссылки на делегаты. В платформе .NET Framework 1.0 и 1.1 API профилирования не позволяет пользователям идентифицировать эти объекты.

В платформе .NET Framework версии 2.0 было добавлено три метода, помогающие профилировщику в точности определить, какие поколения были подобраны и когда, а также идентифицировать корневые объекты. Эти методы помогают профилировщику определить, почему объекты сохранились после сборки мусора.

Метод ICorProfilerCallback2::RootReferences2 позволяет профилировщику определить объекты, которые сохраняются благодаря особым дескрипторам. Сведения о привязках поколений, предоставленные методом ICorProfilerInfo2::GetGenerationBounds, в сочетании со сведениями о подобранных поколениях, предоставленными методом ICorProfilerCallback2::GarbageCollectionStarted, позволяют профилировщику определить объекты, существующие в поколениях, которые не были подобраны.

Ссылки

Метод ICorProfilerCallback::MovedReferences

Метод ICorProfilerCallback2::SurvivingReferences

Метод ICorProfilerInfo2::GetGenerationBounds

См. также

Основные понятия

Общие сведения о профилировании