分析 API 中的对象跟踪

垃圾回收将收回死对象占用的内存,并可能压缩释放的空间。 因此,活动对象将被移到堆内。 本主题说明对象移动将如何影响 ObjectID 值,以及如何通过在压缩和非压缩垃圾回收过程中分析 API 来跟踪这些值。

对象移动

移动了对象后,由以前的通知分配的 ObjectID 值将发生更改。 对象本身的内部状态不会发生更改(它对其他对象的引用除外)。 只有对象在内存中的位置会发生更改(因此其 ObjectID 也会更改)。 利用 ICorProfilerCallback::MovedReferences 通知,探查器可以更新其按 ObjectID 跟踪信息的内部表。 MovedReferences 方法的名称可能会令人在某种程度上产生误解,因为此方法甚至会针对未移动的对象发出。

堆中的对象可能有数千乃至数百万个。 通过提供每个对象的前后 ID 来跟踪这么大数量对象的移动是不现实的。 因此,垃圾回收器趋向于移动块中连续的活动对象,这样,这些对象在堆中的新位置中仍然保持连续。 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 版通过以下新方法为此方案提供了更好的支持:

请注意,一个垃圾回收针对一代可以是压缩垃圾回收,而针对另一代可以是非压缩垃圾回收。 也就是说,任何给定的代对于给定的垃圾回收将收到 SurvivingReferencesMovedReferences 回调,但不会同时收到两者。

备注

进行垃圾回收后,应用程序将停止,直至运行时完成将有关堆的信息传递到代码探查器的过程。 您可以使用 ICorProfilerInfo::GetClassFromObject 方法来获取对象的类的 ClassID。 您可以使用 ICorProfilerInfo::GetClassIDInfoICorProfilerInfo2::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 方法

请参见

概念

分析概述