Algoritmo de recolección de basura Xamarin Android
mono garbage-collection (1)
Estoy leyendo los documentos de la colección de basura Xamarin.Android sobre cómo ayudar al GC a tener un mejor rendimiento al reducir las instancias a las que se hace referencia .
La sección comienza diciendo:
Siempre que se explore una instancia de un tipo o subclase de Java.Lang.Object durante el GC, también se debe escanear todo el gráfico de objetos al que hace referencia la instancia. El gráfico de objetos es el conjunto de instancias de objeto al que hace referencia la "instancia de raíz", más todo a lo que se hace referencia a lo que se refiere la instancia de raíz, recursivamente.
... lo cual entiendo
A continuación, muestra una clase personalizada que hereda de la clase de actividad estándar. Esta clase de actividad personalizada tiene un campo que es una lista de cadenas que se inicializa en el constructor para tener 10,000 cadenas. Se dice que esto es malo porque todas las 10,000 instancias tendrán que escanearse para poder alcanzarlas durante el GC. Eso también lo entiendo
La parte que no me queda claro, es la solución recomendada: dice que el campo List<string>
debe mover a otra clase que no hereda de Java.Lang.Object
y luego se debe hacer referencia a una instancia de esa clase desde la actividad al igual que la lista estaba siendo referenciada anteriormente.
Mi pregunta: ¿cómo ayuda empujar un campo más profundo en el gráfico de objetos al GC cuando el número total de instancias todavía es 10,000 y el párrafo de apertura dice que serán escaneados eventualmente porque el proceso es recursivo?
Como nota al margen, también estoy leyendo ( aquí ) en el SGen GC utilizado por Mono en Android y el proceso transversal del gráfico de objetos se describe como el primero en amplitud comenzando con las raíces de GC. Esto explica cómo una lista de 10,000 elementos provocará una pausa más larga del GC a medida que se verifique cada elemento, pero aún no explica cómo mover esa lista más profundamente en el gráfico ayudará porque el GC eventualmente lo escaneará a medida que profundiza en el gráfico.
Trataré de explicar esto lo mejor que pueda, y no estoy cerca de un experto aquí, así que cualquiera que quiera intervenir, por favor hágalo.
Cuando nos referimos a hacer una peer walk
, estamos ubicando las roots
y atravesando el gráfico de referencia en vivo para ver qué es accesible y qué no:
Objetos raíz:
- Objetos apuntados por campos / propiedades estáticos
- Objetos en la pila de cada subproceso administrado
- Objetos que se han pasado a las API nativas
Básicamente, debe lidiar con dos GC administrados. Los llamaremos Xamarin GC y Android GC como referencia.
Xamarin.Android tiene peer objects
similares que se utilizan para hacer referencia a los objetos nativos de Java conocidos en la JVM de Android. Implementan una interfaz central:
namespace Android.Runtime
{
public interface IJavaObject : IDisposable
{
// JNI reference to the Java object it is wrapping.
// Also known as a pointer to the JVM object
public IntPtr Handle { get; set; }
...
}
}
Siempre que tengamos un objeto con IJavaObject
heredado, mantendrá una referencia fuerte a través de ese identificador JNI anterior para garantizar que se mantenga activo mientras el objeto administrado esté activo.
Piénsalo de esta manera:
IJavaObject
-> IntPtr Handle
-> Java Object
En términos de CG, se representaría de la siguiente manera:
Allocated and collected by Xamarin GC
-> GC Root
-> Allocated and collected by Android GC
Luego tenemos un proceso de GC en Xamarin. Android:
Cuando se ejecuta el GC, puede ver que reemplazará un controlador JNI fuerte con una referencia débil y luego invocar el GC de Android que recogerá nuestro objeto Java. Debido a esto, los peers
se escanean en busca de cualquier relación para asegurarse de que estén reflejados en la JVM. Esto evita que estos objetos se recopilen prematuramente.
Una vez que esto sucede, ejecutamos el Android GC y, cuando termine, recorreremos los objetos similares y comprobaremos las referencias débiles.
- Si un objeto se ha ido, lo recogemos en el lado C #
- Si un objeto aún existe, cambiamos la referencia débil a un mango fuerte JNI
Por lo tanto, este gráfico debe verificarse y actualizarse cada vez que un GC se ejecuta en objetos peer
. Es por eso que es mucho más lento para estos objetos tipo envoltura porque todo el gráfico de objetos debe escanearse comenzando por el objeto similar.
Entonces, cuando hay gráficos de objetos significativos que utiliza nuestro objeto peer
, podemos ayudar al proceso GC moviendo el almacenamiento de las referencias fuera de la clase peer
. Esto generalmente se hace rooting
nuestra referencia independientemente del par. Y dado que no está almacenado como un campo, el GC no tratará de hacer una caminata de relación en el gráfico de objetos.
Como se señaló anteriormente, este no es un gran problema del que preocuparse hasta que note GC largos. Luego puede usar esto como una solución.
Crédito de la imagen: Xamarin University ( https://www.xamarin.com/university )