tiradero teléfono servicio recolección recoleccion número cuál compañias cerca basura c# .net garbage-collection heap

c# - teléfono - servicio de recolección de basura



¿Se actualizan las referencias cuando los recolectores de basura mueven datos en el montón? (2)

Leí que GC (recolectores de basura) mueve los datos en Heap por razones de rendimiento, lo cual no entiendo bien porque es una memoria de acceso aleatorio, tal vez para un mejor acceso secuencial, pero me pregunto si las referencias en Stack se actualizan cuando ocurre tal movimiento. en el montón. Pero tal vez la dirección de compensación sigue siendo la misma, pero otras partes de los datos son movidas por los recolectores de basura, aunque no estoy seguro.

Creo que esta pregunta está relacionada con los detalles de la implementación, ya que no todos los recolectores de basura pueden realizar dicha optimización o pueden hacerlo pero no actualizan las referencias (si es una práctica común entre las implementaciones del recolector de basura). Pero me gustaría obtener alguna respuesta general específica para los recolectores de basura CLR (Common Language Runtime).

Y también estaba leyendo el artículo de Eric Lippert "Las referencias no son direcciones" here , y el siguiente párrafo me confundió un poco:

Si piensa que una referencia es en realidad un controlador opaco de GC, entonces queda claro que para encontrar la dirección asociada con el identificador debe "arreglar" el objeto de alguna manera. Debe indicar al GC "hasta nuevo aviso, el objeto con este identificador no se debe mover en la memoria, ya que alguien podría tener un puntero interior". (Hay varias formas de hacer eso que están más allá del alcance de esta regla).

Suena como para los tipos de referencia, no queremos que se muevan los datos. Entonces, ¿qué más almacenamos en el montón, que podemos movernos para optimizar el rendimiento? Tal vez la información del tipo que almacenamos allí? Por cierto, en caso de que se pregunte de qué se trata ese artículo, entonces Eric Lippert está comparando las referencias con los punteros y trata de explicar cómo puede ser incorrecto decir que las referencias son solo direcciones, aunque es así como C # lo implementa.

Y también, si alguna de mis suposiciones anteriores es incorrecta, corríjame.


En cuanto a C ++ / CLI En Acción , hay una sección sobre punteros interiores vs punteros de anclaje:

C ++ / CLI proporciona dos tipos de punteros que solucionan este problema. El primer tipo se llama puntero interior, que se actualiza con el tiempo de ejecución para reflejar la nueva ubicación del objeto al que se apunta cada vez que se reubica el objeto. La dirección física apuntada por el puntero interior nunca permanece igual, pero siempre apunta al mismo objeto. El otro tipo se llama puntero de anclaje, lo que evita que el GC reubique el objeto; en otras palabras, fija el objeto a una ubicación física específica en el montón CLR. Con algunas restricciones, las conversiones son posibles entre el interior, la fijación y los punteros nativos.

A partir de eso, puede concluir que los tipos de referencia se mueven en el montón y sus direcciones sí cambian. Después de la fase de marca y barrido, los objetos se compactan dentro del montón, por lo que en realidad se mueven a nuevas direcciones. El CLR es responsable de realizar un seguimiento de la ubicación de almacenamiento real y actualizar esos punteros interiores utilizando una tabla interna, asegurándose de que cuando se accede a ella, todavía apunte a la ubicación válida del objeto.

Hay un ejemplo tomado de aquí:

ref struct CData { int age; }; int main() { for(int i=0; i<100000; i++) // ((1)) gcnew CData(); CData^ d = gcnew CData(); d->age = 100; interior_ptr<int> pint = &d->age; // ((2)) printf("%p %d/r/n",pint,*pint); for(int i=0; i<100000; i++) // ((3)) gcnew CData(); printf("%p %d/r/n",pint,*pint); // ((4)) return 0; }

Lo que se explica:

En el código de ejemplo, crea 100.000 objetos CData huérfanos ((1)) para que pueda llenar una buena parte del montón de CLR. Luego crea un objeto CData que está almacenado en una variable y ((2)) un puntero interior a la edad del miembro int de este objeto CData. A continuación, imprime la dirección del puntero y el valor int al que se apunta. Ahora, ((3)) creas otros 100,000 objetos CData huérfanos; En algún lugar a lo largo de la línea, se produce un ciclo de recolección de basura (los objetos huérfanos creados anteriormente ((1)) se recolectan porque no están referenciados en ninguna parte). Tenga en cuenta que no utiliza una llamada GC :: Collect porque no está garantizado que esto obligue a un ciclo de recolección de basura. Como ya ha visto en la discusión del algoritmo de recolección de basura en el capítulo anterior, el GC libera espacio al eliminar los objetos huérfanos para que pueda realizar más asignaciones. Al final del código (momento en el que se produjo una recolección de basura), nuevamente ((4)) imprime la dirección del puntero y el valor de la antigüedad. Esta es la salida que obtuve en mi máquina (tenga en cuenta que las direcciones variarán de una máquina a otra, por lo que sus valores de salida no serán los mismos):

012CB4C8 100 012A13D0 100


Sí, las referencias se actualizan durante una recolección de basura. Necesariamente, los objetos se mueven cuando se compacta el montón. La compactación tiene dos propósitos principales:

  • hace que los programas sean más eficientes al usar los cachés de datos del procesador de manera más eficiente. Ese es un acuerdo muy, muy importante en los procesadores modernos, la memoria RAM es extremadamente lenta en comparación con el motor de ejecución, dos grandes órdenes de magnitud. El procesador puede detenerse para recibir cientos de instrucciones cuando tiene que esperar a que la RAM suministre un valor variable.
  • Resuelve el problema de fragmentación que sufren los montones. La fragmentación se produce cuando se libera un objeto pequeño que está rodeado de objetos vivos. Un agujero que no se puede utilizar para nada más que un objeto de tamaño igual o menor. Malo para la eficiencia de uso de memoria y la eficiencia del procesador. Tenga en cuenta que la LOH, la gran pila de objetos en .NET, no se compacta y, por lo tanto, sufre este problema de fragmentación. Muchas preguntas sobre eso en SO.

A pesar de la didáctica de Eric, una referencia de objeto realmente es solo una dirección. Un puntero, exactamente del mismo tipo que usaría en un programa C o C ++. Muy eficiente, necesariamente así. Y todo lo que GC tiene que hacer después de mover un objeto es actualizar la dirección almacenada en ese puntero al objeto movido. El CLR también permite asignar identificadores a objetos, referencias adicionales . Expuesto como el tipo GCHandle en .NET, pero solo es necesario si el GC necesita ayuda para determinar si un objeto debe permanecer vivo o no debe moverse. Solo es relevante si se interopera con código no administrado.

Lo que no es tan simple es encontrar ese puntero hacia atrás. El CLR realiza una gran inversión para garantizar que se pueda realizar de manera confiable y eficiente. Tales punteros se pueden almacenar en muchos lugares diferentes. Las más fáciles de encontrar son las referencias de objetos almacenadas en un campo de un objeto, una variable estática o un GCHandle. Los más difíciles son los punteros almacenados en la pila del procesador o en un registro de la CPU. Sucede para argumentos de método y variables locales por ejemplo.

Una garantía que el CLR debe proporcionar para que eso suceda es que el GC siempre puede caminar de manera confiable en la pila de un hilo. Por lo tanto, puede encontrar variables locales que están almacenadas en un marco de pila. Entonces, necesita saber dónde buscar en un marco de pila de ese tipo, ese es el trabajo del compilador JIT. Cuando compila un método, no solo genera el código de máquina para el método, sino que también crea una tabla que describe dónde se almacenan esos punteros. Encontrarás más detalles sobre eso en este post .