Recolección de elementos no utilizados
Xamarin.Android usa el recolector de elementos no utilizados Simple Generational deMono. Se trata de un recolector de elementos no utilizados de marca y barrido con dos generaciones y un espacio de objetos grandes ,con dos tipos de colecciones:
- Colecciones secundarias (recopila el montón gen0)
- Colecciones principales (recopila montones de espacio de objetos grandes y Gen1).
Nota:
En ausencia de una colección explícita a través de GC. Las colecciones collect() son a petición,en función de las asignaciones del montón. No se trata de un sistema de recuento de referencias; Los objetos no se recopilarán en cuanto no haya referencias pendienteso cuando se haya salido de un ámbito. La GC se ejecutará cuando el montón menor se haya quedo sin memoria para las nuevas asignaciones. Si no hay asignaciones, no se ejecutará.
Las colecciones secundarias son baratas y frecuentes, y se usan para recopilar objetos asignados recientemente y fallido. Las colecciones secundarias se realizan después de cada pocos MB de objetos asignados. Las colecciones secundarias se pueden realizar manualmente mediante una llamada a GC. Recopilar (0)
Las colecciones principales son costosas y menos frecuentes, y se usan para reclamar todos los objetos fallados. Las colecciones principales se realizan una vez que se agota la memoria para el tamaño actual del montón (antes de volver a tamaño del montón). Las colecciones principales se pueden realizar manualmente mediante una llamada a GC. Recopile () o llame a GC. Recopilar (int) con el argumento GC. MaxGeneration.
Colecciones de objetos entre máquinas virtuales
Hay tres categorías de tipos de objeto.
Objetos administrados:tipos que no heredan de Java.Lang.Object , por ejemplo, System.String. Estos se recopilan normalmente por el GC.
Objetos de Java:tipos de Java que están presentes dentro de la máquina virtual en tiempo de ejecución de Android, pero que no se exponen a la máquina virtual Mono. Se trata de un tema muy pesado y no se analizará aún más. Estos se recopilan normalmente mediante la máquina virtual en tiempo de ejecución de Android.
Objetos delmismo nivel: tipos que implementan IJavaObject, por ejemplo, todas las subclases Java.Lang.Object y Java.Lang.Throwable. Las instancias de estos tipos tienen dos "mitades" un par administrado y un del mismo nivel nativo. El elemento del mismo nivel administrado es una instancia de la clase de C#. El elemento del mismo nivel nativo es una instancia de una clase java dentro de la máquina virtual en tiempo de ejecución de Android y la propiedad IJavaObject.Handle de C# contiene una referencia global de JNI al elemento del mismo nivel nativo.
Hay dos tipos de elementos nativos del mismo nivel:
Pares del marco: tipos de Java "normales" que no saben nada de Xamarin.Android, por ejemplo, android.content.Context.
Emparejamientos de usuarios: contenedores que se pueden llamar de Android que se generan en tiempo de compilación para cada subclase Java.Lang.Object presente dentro de la aplicación.
Como hay dos máquinas virtuales dentro de un proceso de Xamarin.Android, hay dos tipos de recolecciones de elementos no utilizados:
- Colecciones en tiempo de ejecución de Android
- Colecciones mono
Las colecciones en tiempo de ejecución de Android funcionan con normalidad, pero con una advertencia: una referencia global de JNI se trata como una raíz de GC. Por lo tanto, si hay una referencia global de JNI que se mantiene en un objeto de máquina virtual en tiempo de ejecución de Android, el objeto no se puede recopilar, aunque sea apto para la recopilación.
Las colecciones mono son donde sucede la diversión. Los objetos administrados se recopilan normalmente. Los objetos del mismo nivel se recopilan realizando el siguiente proceso:
Todos los objetos del mismo nivel aptos para la colección Mono tienen su referencia global de JNI reemplazada por una referencia global débil de JNI.
Se invoca una GC de máquina virtual en tiempo de ejecución de Android. Se puede recopilar cualquier instancia nativa del mismo nivel.
Se comprueban las referencias globales débiles de JNI creadas en (1). Si se ha recopilado la referencia débil, se recopila el objeto Del mismo nivel. Si no se ha recopilado la referencia débil, la referencia débil se reemplaza por una referencia global de JNI y no se recopila el objeto Del mismo nivel. Nota: En la API 14+, esto significa que el valor devuelto de
IJavaObject.Handlepuede cambiar después de una GC.
El resultado final de todo esto es que una instancia de un objeto Del mismo nivel estará en funcionamiento siempre que se haga referencia a él mediante código administrado (por ejemplo, almacenado en una variable) o al que haga referencia el código static java. Además, la duración de los pares nativos se ampliará más allá de lo que tendrían de lo contrario, ya que el mismo nivel nativo no se podrá recopilar hasta que se recopile el par nativo y el del mismo nivel administrado.
Ciclos de objeto
Los objetos del mismo nivel están presentes lógicamente tanto en el entorno de ejecución de Android como en las máquinas virtuales Mono. Por ejemplo, una instancia del mismo nivel administrada Android.App.Activity tendrá una instancia de Java del mismo nivel android.app.Activity Framework correspondiente. Se puede esperar que todos los objetos que heredan de Java.Lang.Object tengan representaciones dentro de ambas máquinas virtuales.
Todos los objetos que tienen representación en ambas máquinas virtuales tendrán duraciones que se extienden en comparación con los objetos que solo están presentes dentro de una sola máquina virtual (por ejemplo, System.Collections.Generic.List<int> ).
Llamada a GC. Recopilar no recopilará necesariamente estos objetos, ya que el GC de Xamarin.Android debe asegurarse de que ninguna máquina virtual haga referencia al objeto antes de recopilarlo.
Para acortar la duración del objeto, se debe invocar Java.Lang.Object.Dispose(). Esto "evitará" manualmente la conexión en el objeto entre las dos máquinas virtuales liberando la referencia global, lo que permite recopilar los objetos más rápidamente.
Colecciones automáticas
A partir de la versión 4.1.0,Xamarin.Android realiza automáticamente una GC completa cuando se supera un umbral gref. Este umbral es el 90 % de los grefs máximos conocidos para la plataforma: 1800 grefs en el emulador (2000 como máximo) y 46800 grefs en hardware (máximo 52000). Nota: Xamarin.Android solo cuenta los grefs creados por Android.Runtime.JNIEnvy no conocerá ningún otro grefs creado en el proceso. Se trata de un solo heurístico.
Cuando se realiza una recopilación automática, se imprimirá un mensaje similar al siguiente en el registro de depuración:
I/monodroid-gc(PID): 46800 outstanding GREFs. Performing a full GC!
La aparición de esto no es determinista y puede producirse en momentos inoportunes (por ejemplo, en medio de la representación de gráficos). Si ve este mensaje, es posible que quiera realizar una colección explícita en otro lugar o que desee intentar reducir la duración de los objetos del mismo nivel.
Opciones de puente de GC
Xamarin.Android ofrece administración de memoria transparente con Android y el entorno de ejecución de Android. Se implementa como una extensión para el recolector de elementos no utilizados Mono denominado puente gc.
El puente de GC funciona durante una recolección de elementos no utilizados mono y averiguar qué objetos del mismo nivel necesitan comprobar su "ejecución" con el montón en tiempo de ejecución de Android. El puente de GC realiza esta determinación mediante los pasos siguientes (en orden):
Induzca el gráfico de referencia mono de objetos del mismo nivel inaccesibles en los objetos de Java que representan.
Realice una GC de Java.
Compruebe qué objetos están realmente fallados.
Este proceso complicado es lo que permite a las subclases de hacer referencia libremente a cualquier objeto; quita las restricciones en las que los objetos de Java se pueden enlazar Java.Lang.Object a C#. Debido a esta complejidad, el proceso de puente puede ser muy costoso y puede provocar pausas notables en una aplicación. Si la aplicación está experimentando pausas significativas, merece la pena investigar una de las tres implementaciones de GC Bridge siguientes:
Tarjan: un diseño completamente nuevo del puente de GC basado en el algoritmo de Robert Tarjan y la propagación de referencia hacia atrás. Tiene el mejor rendimiento en nuestras cargas de trabajo simuladas, pero también tiene la mayor parte de código experimental.
Nuevo: una revisión importante del código original, que corrige dos instancias de comportamiento cuadrático, pero mantiene el algoritmo principal (basado en el algoritmo de Rsaaraju para buscar componentes fuertemente conectados).
Anterior: la implementación original (se considera la más estable de las tres). Este es el puente que debe usar una aplicación si las
GC_BRIDGEpausas son aceptables.
La única manera de averiguar qué puente de GC funciona mejor es experimentando en una aplicación y analizando la salida. Hay dos maneras de recopilar los datos para la prueba comparativa:
Habilitar registro: habilite el registro (como se describe en la sección Configuración) para cada opción de puente de GC y, a continuación, capture y compare las salidas del registro de cada configuración. Inspeccione los
GCmensajes de cada opción; en concreto, losGC_BRIDGEmensajes. Las pausas de hasta 150 ms para las aplicaciones no interactivas son tolerables, pero las pausas por encima de 60 ms para aplicaciones muy interactivas (como juegos) son un problema.Habilitar la contabilidad de puentes: la contabilidad de puente mostrará el costo medio de los objetos a los que apunta cada objeto implicado en el proceso de puente. La ordenación de esta información por tamaño proporcionará sugerencias sobre lo que mantiene la mayor cantidad de objetos adicionales.
El valor predeterminado es Tarjan. Si encuentra una regresión, puede que sea necesario establecer esta opción en Anterior. Además, puede optar por usar la opción Anterior más estable si Tarjan no produce una mejora en el rendimiento.
Para especificar qué GC_BRIDGE opción debe usar una aplicación, pase o a la variable de entorno bridge-implementation=oldbridge-implementation=newbridge-implementation=tarjanMONO_GC_PARAMS . Esto se logra agregando un nuevo archivo al proyecto con una acción de compilación de . Por ejemplo:
MONO_GC_PARAMS=bridge-implementation=tarjan
Para obtener más información, vea Configuración.
Ayuda a gc
Hay varias maneras de ayudar al GC a reducir el uso de memoria y los tiempos de recopilación.
Eliminación de instancias del mismo nivel
El GC tiene una vista incompleta del proceso y puede no ejecutarse cuando la memoria es baja porque el GC no sabe que la memoria es baja.
Por ejemplo, una instancia de un tipo Java.Lang.Object o un tipo derivado tiene al menos 20 bytes de tamaño (sujeto a cambios sin previo aviso, etc.). Los contenedores administrados que se pueden llamar no agregan miembros de instancia adicionales, por lo que si tiene una instancia de Android.Graphics.Bitmap que hace referencia a un blob de memoria de 10 MB, la GC de Xamarin.Android no lo sabrá: la GC verá un objeto de 20 bytes y no podrá determinar que está vinculado a objetos asignados en tiempo de ejecución de Android que mantienen activos 10 MB de memoria.
Con frecuencia es necesario ayudar a la GC. Desafortunadamente, GC. AddMemoryPressure() y GC. No se admite RemoveMemoryPressure(), por lo que si sabe que acaba de liberar un gráfico de objetos asignados a Java de gran tamaño, es posible que tenga que llamar manualmente a GC. Collect() para solicitar a una recolección de elementos no utilizados que libere la memoria del lado Java, o bien puede eliminar explícitamente las subclases Java.Lang.Object, lo que romperá la asignación entre el contenedor administrado al que se puede llamar y la instancia de Java. Por ejemplo, vea Error 1084.
Nota:
Debe tener mucho cuidado al eliminar instancias de subclase.
Para minimizar la posibilidad de daños en la memoria, observe las siguientes directrices al llamar a Dispose() .
Uso compartido entre varios subprocesos
Si la instancia de Java o administrada se puede compartir entre varios subprocesos, no debe ser d, nunca. Por ejemplo,Typeface.Create()puede devolver una instancia Typeface.Create() Si varios subprocesos proporcionan los mismos argumentos, obtendrán la misma instancia. Por lo tanto, la ing de la instancia de un subproceso puede invalidar otros subprocesos, lo que puede dar lugar a s de Dispose() (entre otros) porque la instancia se ha eliminado TypefaceArgumentException de otro JNIEnv.CallVoidMethod() subproceso.
Eliminación de tipos de Java enlazados
Si la instancia es de un tipo de Java enlazado, la instancia se puede eliminar siempre que la instancia no se reutilice del código administrado y la instancia de Java no se pueda compartir entre los subprocesos (consulte la explicación anterior). (Realizar esta determinación puede ser difícil). La próxima vez que la instancia de Java escriba código administrado, se creará un nuevo contenedor para él.
Esto suele ser útil en lo que respecta a Drawables y otras instancias con muchos recursos:
using (var d = Drawable.CreateFromPath ("path/to/filename"))
imageView.SetImageDrawable (d);
Lo anterior es seguro porque el valor del mismo nivel que drawable.CreateFromPath() devuelve hará referencia a un par framework, no a un usuario del mismo nivel. La llamada al final del bloque interrumpirá la relación entre las instancias Dispose()usingDispose() administradas y using del marco, lo que permitirá recopilar la instancia de Java en cuanto el entorno de ejecución de Android necesite. Esto no sería seguro si la instancia del mismo nivel hace referencia a un usuario del mismo nivel; aquí se usa información "externa" para saber que no puede hacer referencia a un usuario del mismo nivel y, por tanto, la llamada es segura.
Eliminación de otros tipos
Si la instancia hace referencia a un tipo que no es un enlace de un tipo de Java (como un personalizado), NO llame a a menos que sepa que ningún código Java llamará a métodos invalidados en Activity esa ActivityDispose() instancia. Dispose()
Si no lo hace, se produce s.
Por ejemplo, si tiene un agente de escucha de clic personalizado:
partial class MyClickListener : Java.Lang.Object, View.IOnClickListener {
// ...
}
No debe eliminar esta instancia, ya que Java intentará invocar métodos en ella en el futuro:
// BAD CODE; DO NOT USE
Button b = FindViewById<Button> (Resource.Id.myButton);
using (var listener = new MyClickListener ())
b.SetOnClickListener (listener);
Usar comprobaciones explícitas para evitar excepciones
Si ha implementado un método de sobrecarga Java.Lang.Object.Dispose, evite tocar objetos que impliquen JNI. Si lo hace, puede crear una situación de doble eliminación que haga posible que el código intente (fatalmente) acceder a un objeto de Java subyacente que ya se ha recopilado como elemento no utilizado. Al hacerlo, se produce una excepción similar a la siguiente:
System.ArgumentException: 'jobject' must not be IntPtr.Zero.
Parameter name: jobject
at Android.Runtime.JNIEnv.CallVoidMethod
Esta situación suele producirse cuando la primera eliminación de un objeto hace que un miembro se convierta en NULL y, a continuación, un intento de acceso posterior en este miembro null hace que se produzca una excepción. En concreto, el objeto (que vincula una instancia administrada a su instancia de Java subyacente) se invalida en la primera eliminación, pero el código administrado sigue intentando acceder a esta instancia de Java subyacente aunque ya no esté disponible Handle (consulte Handle que se pueden llamar para obtener más información sobre la asignación entre instancias de Java e instancias administradas).
Una buena manera de evitar esta excepción es comprobar explícitamente en el método que la asignación entre la instancia administrada y la instancia de Java subyacente sigue siendo válida; es decir, compruebe si el objeto es null ( ) antes de acceder a sus DisposeHandleIntPtr.Zero miembros. Por ejemplo, el método Dispose siguiente tiene acceso a un objeto childViews :
class MyClass : Java.Lang.Object, ISomeInterface
{
protected override void Dispose (bool disposing)
{
base.Dispose (disposing);
for (int i = 0; i < this.childViews.Count; ++i)
{
// ...
}
}
}
Si un paso de dispose inicial childViews hace que tenga un valor no Handle válido, el acceso de bucle produce una forArgumentException excepción . Al agregar una comprobación Handle nula explícita antes del primer acceso, el método siguiente impide que se produzca la childViewsDispose excepción:
class MyClass : Java.Lang.Object, ISomeInterface
{
protected override void Dispose (bool disposing)
{
base.Dispose (disposing);
// Check for a null handle:
if (this.childViews.Handle == IntPtr.Zero)
return;
for (int i = 0; i < this.childViews.Count; ++i)
{
// ...
}
}
}
Reducir instancias a las que se hace referencia
Cada vez que se examina una instancia de un tipo o subclase durante la GC, también se debe examinar todo el gráfico de objetos al que hace Java.Lang.Object referencia la instancia. Java.Lang.Object El gráfico de objetos es el conjunto de instancias de objeto a las que hace referencia la "instancia raíz", además de todo lo que hace referencia a lo que hace referencia la instancia raíz, de forma recursiva.
Considere la siguiente clase:
class BadActivity : Activity {
private List<string> strings;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
strings.Value = new List<string> (
Enumerable.Range (0, 10000)
.Select(v => new string ('x', v % 1000)));
}
}
Cuando se construye, el gráfico de objetos contendrá BadActivity 10 004 instancias (1x, BadActivity 1x, strings 1x string[] held by , strings 10000x string instances), BadActivityBadActivity todas las cuales deban examinarse cada vez que se examina la instancia.
Esto puede tener efectos perjudiciales en los tiempos de recopilación, lo que da lugar a mayores tiempos de pausa de GC.
Puede ayudar a la GC mediante la reducción del tamaño de los gráficos de objetos cuya raíz son las instancias del mismo nivel de usuario. En el ejemplo anterior, esto se puede hacer si se pasa a una clase independiente que BadActivity.strings no hereda de Java.Lang.Object:
class HiddenReference<T> {
static Dictionary<int, T> table = new Dictionary<int, T> ();
static int idgen = 0;
int id;
public HiddenReference ()
{
lock (table) {
id = idgen ++;
}
}
~HiddenReference ()
{
lock (table) {
table.Remove (id);
}
}
public T Value {
get { lock (table) { return table [id]; } }
set { lock (table) { table [id] = value; } }
}
}
class BetterActivity : Activity {
HiddenReference<List<string>> strings = new HiddenReference<List<string>>();
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
strings.Value = new List<string> (
Enumerable.Range (0, 10000)
.Select(v => new string ('x', v % 1000)));
}
}
Colecciones secundarias
Las colecciones secundarias se pueden realizar manualmente mediante una llamada a GC. Collect(0). Las colecciones secundarias son económicas (en comparación con las colecciones principales), pero tienen un costo fijo significativo, por lo que no desea desencadenarlas con demasiada frecuencia y debe tener un tiempo de pausa de unos milisegundos.
Si la aplicación tiene un "ciclo de servicio" en el que se hace lo mismo una y otra vez, puede ser aconsejable realizar manualmente una recopilación secundaria una vez finalizado el ciclo de servicio. Los ciclos de servicio de ejemplo incluyen:
- Ciclo de representación de un solo fotograma de juego.
- Toda la interacción con un cuadro de diálogo de aplicación determinado (abrir, rellenar, cerrar)
- Un grupo de solicitudes de red para actualizar o sincronizar los datos de la aplicación.
Colecciones principales
Las colecciones principales se pueden realizar manualmente mediante una llamada a GC. Collect() o .
Se deben realizar con raras ocasiones y pueden tener un tiempo de pausa de un segundo en un dispositivo de estilo Android al recopilar un montón de 512 MB.
Las colecciones principales solo se deben invocar manualmente, si es que alguna vez:
Al final de largos ciclos de servicio y cuando una pausa larga no presenta un problema al usuario.
Dentro de un método Android.App.Activity.OnLowMemory() invalidado.
Diagnóstico
Para realizar un seguimiento de cuándo se crean y destruyen referencias globales, puede establecer la propiedad del sistema debug.mono.log para que contenga gref o gc.
Configuración
El recolector de elementos no utilizados de Xamarin.Android se puede configurar estableciendo la MONO_GC_PARAMS variable de entorno . Las variables de entorno se pueden establecer con una acción de compilación de AndroidEnvironment.
La MONO_GC_PARAMS variable de entorno es una lista separada por comas de los parámetros siguientes:
nursery-size=nursery-sizeestablece el tamaño de la zona. El tamaño se especifica en bytes y debe ser una potencia de dos. Los sufijosk, y se pueden usar para especificarmgkilo-, mega- y gigabytes, respectivamente. La primera generación (de dos) es la primera. Normalmente, un mayor tamaño acelerará el programa, pero obviamente usará más memoria. Tamaño predeterminado de 512 kb.soft-heap-limit=soft-heap-limitel consumo máximo de memoria administrada de destino para la aplicación. Cuando el uso de memoria está por debajo del valor especificado, el GC se optimiza para el tiempo de ejecución (menos colecciones). Por encima de este límite, el GC está optimizado para el uso de memoria (más colecciones).evacuation-threshold=evacuation-thresholdestablece el umbral de umbral de umbral en porcentaje. El valor debe ser un entero en el intervalo comprendido entre 0 y 100. El valor predeterminado es 66. Si la fase de barrido de la colección encuentra que la ocupación de un tipo de bloque de montón específico es menor que este porcentaje, realizará una recopilación de copia para ese tipo de bloque en la siguiente colección principal, con lo que se restaurará la ocupación a casi el 100 %. Un valor de 0 desactiva la desasejo.bridge-implementation=bridge-implementationse establecerá la opción Puente de GC para ayudar a solucionar problemas de rendimiento de GC. Hay tres valores posibles: old , new , tarjan.bridge-require-precise-merge: el puente de Tarjan contiene una optimización que, en raras ocasiones, puede provocar que un objeto se re recolecte un GC después de que se convierta en un elemento no utilizados por primera vez. Incluir esta opción deshabilita esa optimización, lo que hace que los GCs sea más predecible, pero potencialmente más lento.
Por ejemplo, para configurar el GC para que tenga un límite de tamaño de montón de 128 MB, agregue un nuevo archivo a la Project con una acción De compilación de con el contenido:
MONO_GC_PARAMS=soft-heap-limit=128m