Seguimiento de objetos en la API de generación de perfiles
La recolección de elementos no utilizados reclama la memoria ocupada por objetos muertos y compacta el espacio liberado. Como resultado, los objetos activos se mueven al montón. En este tema se explica cómo el movimiento de objetos afecta a los valores de ObjectID, y cómo la API de generación de perfiles realiza el seguimiento de estos valores durante la recolección de elementos no utilizados con compactación y sin compactación.
Movimiento del objeto
Cuando se mueven objetos, los valores ObjectID asignados por notificaciones anteriores cambian. El estado interno del propio objeto no cambia, salvo sus referencias a otros objetos. Solamente cambia la ubicación del objeto en la memoria (y por consiguiente su ObjectID). La notificación ICorProfilerCallback::MovedReferences permite a los generadores de perfiles actualizar sus tablas internas que realizan el seguimiento de información por ObjectID. El nombre del método MovedReferences es en cierto modo engañoso, porque se emite incluso para objetos que no se movieron.
El número de objetos en el montón puede ser miles o millones. No sería práctico realizar el seguimiento del movimiento de un número tan grande de objetos proporcionando un Id. anterior y otro posterior para cada objeto. Por consiguiente, el recolector de elementos no utilizados tiende a mover los objetos activos contiguos en bloques, de modo que quedan contiguos en sus nuevas ubicaciones en el montón. La notificación MovedReferences informa de los valores anteriores y posteriores de ObjectID de estos bloques contiguos de objetos.
Suponga que un valor de ObjectID (oldObjectID) está en el siguiente intervalo:
oldObjectIDRangeStart[i] <= oldObjectID < oldObjectIDRangeStart[i] + cObjectIDRangeLength[i]
En este caso, el desplazamiento desde el inicio del intervalo al inicio del objeto es como sigue:
oldObjectID - oldObjectRangeStart[i]
Para cualquier valor de i que está en el intervalo siguiente:
0 <= i < cMovedObjectIDRanges
puede calcular el nuevo ObjectID como sigue:
newObjectID = newObjectIDRangeStart[i] + (oldObjectID – oldObjectIDRangeStart[i])
Todas estas devoluciones de llamada se realizan mientras está suspendido Common Language Runtime (CLR). Por consiguiente, ninguno de los valores de ObjectID puede cambiar hasta que el motor en tiempo de ejecución se reanuda y se produce otra recopilación de elementos no utilizados.
La ilustración siguiente muestra 10 objetos antes de la recolección de elementos no utilizados. Sus direcciones de inicio (equivalente a ObjectID) son 08, 09, 10, 12, 13, 15, 16, 17, 18 y 19. Los objetos con ObjectID 09, 13 y 19 están inactivos, y su espacio se recuperará durante la recolección de elementos no utilizados.
Movimiento de objetos durante la recolección de elementos no utilizados
La parte inferior de la ilustración muestra los objetos después de la recolección de elementos no utilizados. El espacio que ocupaban los objetos inactivos se ha reclamado para contener objetos activos. Los objetos activos del montón se han movido a las nuevas ubicaciones que se muestran. Como resultado, su ObjectID cambiará. La tabla siguiente muestra los ObjectID antes y después de la recolección de elementos no utilizados.
Objeto |
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 |
La tabla siguiente compacta la información especificando las posiciones iniciales y los tamaños de los bloques contiguos. Esta tabla muestra exactamente cómo devuelve la información el método MovedReferences.
Bloques |
oldObjectIDRangeStart[] |
newObjectIDRangeStart[] |
cObjectIDRangeLength[] |
---|---|---|---|
0 |
08 |
07 |
1 |
1 |
10 |
08 |
2 |
2 |
15 |
11 |
4 |
Detectar todos los objetos eliminados
El método MovedReferences informa de todos los objetos que sobreviven una recolección de elementos no utilizados con compactación, sin tener en cuenta si se movieron. Cualquier objeto del que no informe MovedReferences no sobrevivió. Sin embargo, no todas las recolecciones de elementos no utilizados realizan compactación. En las versiones 1.0 y 1.1 de .NET Framework, el generador de perfiles no podía detectar los objetos que sobrevivieran a una recolección de elementos no utilizados sin compactación (una recopilación de elementos no utilizados en la que no se mueve ningún objeto en absoluto). La versión 2.0 de .NET Framework proporciona mejor compatibilidad para este escenario a través de los nuevos métodos siguientes:
El generador de perfiles puede llamar al método ICorProfilerInfo2::GetGenerationBounds para obtener los límites de los segmentos del montón de recolección de elementos no utilizados. El campo rangeLength de la estructura COR_PRF_GC_GENERATION_RANGE resultante se puede utilizar para determinar la extensión de los objetos activos en una generación compactada.
La devolución de llamada ICorProfilerCallback2::GarbageCollectionStarted indica qué generaciones está recogiendo la recolección de elementos no utilizados actual. Todos los objetos que están en una generación que no se recolecte sobrevivirán a la recolección de elementos no utilizados.
La devolución de llamada ICorProfilerCallback2::SurvivingReferences indica qué objetos sobrevivieron a una recolección de elementos no utilizados sin compactación.
Una sola recolección de elementos no utilizados se puede comprimir para una generación y se puede dejar sin comprimir para otra. Es decir, cualquier generación determinada recibirá devoluciones de llamada SurvivingReferences o MovedReferences para una recolección de elementos no utilizados determinada, pero no ambas.
Comentarios
Después de una recolección de elementos no utilizados, una aplicación se detiene hasta que el motor en tiempo de ejecución haya terminado de pasar información sobre el montón al generador de perfiles del código. Puede utilizar el método ICorProfilerInfo::GetClassFromObject para obtener la clase del objeto ClassID. Puede utilizar el método ICorProfilerInfo::GetClassIDInfo o ICorProfilerInfo2::GetClassIDInfo2 para obtener información de metadatos sobre la clase.
En las versiones 1.0 y 1.1 de .NET Framework, cuando una operación de recolección de elementos no utilizados se ha completado, se espera que cada objeto superviviente sea una referencia raíz, que tenga un elemento primario que sea una referencia raíz, o que exista en una generación que no se haya recogido. A veces, es posible tener objetos que no pertenezcan a ninguna de estas categorías. Estos objetos son asignados internamente por el motor en tiempo de ejecución o son referencias débiles a delegados. En .NET Framework 1.0 y 1.1, la API de generación de perfiles no permite al usuario identificar estos objetos.
En la versión 2.0 de .NET Framework, se agregaron tres métodos para ayudar al generador de perfiles a clarificar exactamente qué generaciones se recolectan y cuándo, y a identificar qué objetos son raíces. Estos métodos ayudan al generador de perfiles a determinar por qué los objetos permanecen después de una recolección:
El método ICorProfilerCallback2::RootReferences2 permite al generador de perfiles identificar objetos que se retienen mediante identificadores especiales. La información de límites de generación proporcionada por el método ICorProfilerInfo2::GetGenerationBounds combinada con la información de generación recolectada proporcionada por el método ICorProfilerCallback2::GarbageCollectionStarted permite al generador de perfiles identificar objetos que existen en generaciones que no se recolectaron.
Referencia
ICorProfilerCallback::MovedReferences (Método)
ICorProfilerCallback2::SurvivingReferences (Método)
ICorProfilerInfo2::GetGenerationBounds (Método)