Resumen de fuga de memoria de Android
El propósito de la gestión de la memoria es ayudarnos de manera efectiva a evitar fugas de memoria en nuestras aplicaciones durante el desarrollo. Todos están familiarizados con las filtraciones de memoria. En pocas palabras, significa que el objeto que debe liberarse no ha sido liberado, y ha sido sostenido por una determinada instancia, pero ya no se usa, de modo que el GC no se puede reciclar. Recientemente, he leído muchos documentos y materiales relevantes. Planeo resumirlos y resolverlos, compartir y aprender con usted, y también advertirme sobre cómo evitar estas situaciones durante la codificación en el futuro y mejorar la experiencia y la calidad de la aplicación.
Comenzaré con los conceptos básicos de las filtraciones de memoria Java y usaré ejemplos específicos para ilustrar las diversas causas de las filtraciones de memoria de Android, así como cómo usar herramientas para analizar las fugas de memoria de la aplicación, y finalmente resumirlas.
Estrategia de asignación de memoria de Java
Hay tres tipos de estrategias de asignación de memoria cuando se ejecutan los programas de Java, a saber, la asignación estática, la asignación de la pila y la asignación de almacenamiento intermedio. En consecuencia, el espacio de memoria utilizado por las tres estrategias de almacenamiento es principalmente un área de almacenamiento estático (también conocido como área de método), área de pila y área de montón.
Área de almacenamiento estático (área de método): principalmente almacena datos estáticos, datos estáticos globales y constantes. Este pedazo de memoria se asigna cuando el programa se compila y existe durante todo el programa.
Área de pila: cuando se ejecuta un método, las variables locales en el cuerpo del método (incluido el tipo de datos básicos y la referencia de objeto) se crean en la pila, y la memoria mantenida por estas variables locales se lanzará automáticamente al final de la ejecución del método. Debido a que la operación de asignación de memoria de pila está integrada en el conjunto de instrucciones del procesador, es muy eficiente, pero la capacidad de memoria asignada es limitada.
Área de montón: también conocida como asignación de memoria dinámica, generalmente se refiere a la memoria que es directamente nueva cuando el programa se está ejecutando, es decir, una instancia del objeto. Cuando esta parte de la memoria no está en uso, el recolector de basura Java será responsable del reciclaje.
La diferencia entre pila y montón:
Algunos tipos básicos de variables definidas en el cuerpo del método y las variables de referencia de los objetos se asignan en la memoria de pila del método. Cuando una variable se define en un bloque de método, Java asignará el espacio de memoria para la variable en la pila. Cuando se excede el alcance de la variable, la variable no será válida, y el espacio de memoria asignado a ella se liberará, y el espacio de memoria se puede reutilizar.
La memoria Heap se usa para almacenar todos los objetos creados por nuevos (incluidas todas las variables de miembros en el objeto) y las matrices. La memoria asignada en el montón será administrada automáticamente por el recolector de basura Java. Después de generar una matriz u objeto en el montón, se puede definir una variable especial en la pila. El valor de esta variable es igual a la primera dirección de la matriz u objeto en la memoria del montón. Esta variable especial es la variable de referencia que mencionamos anteriormente. Podemos acceder a objetos o matrices en el montón a través de esta variable de referencia.
Por ejemplo:
Muestra de clase pública {int s1 = 0; muestra msample1 = new sample (); public void Method () {int s2 = 1; muestra msample2 = new sample ();}} muestra msample3 = new sample (); La variable local S2 de la clase de muestra y la variable de referencia MSample2 existen en la pila, pero el objeto apuntado por msample2 existe en el montón.
La entidad del objeto señalada por Msample3 se almacena en el montón, incluidas todas las variables de miembros S1 y MSample1 de este objeto, y existe en la pila.
en conclusión:
Los tipos de datos básicos y las referencias de las variables locales se almacenan en la pila, y las entidades de objeto referenciadas se almacenan en el montón. - Debido a que pertenecen a variables en los métodos, el ciclo de vida termina con los métodos.
Las variables miembros están todas almacenadas y en el montón (incluidos los tipos de datos básicos, entidades de objetos referenciadas y referenciadas), debido a que pertenecen a clases, los objetos de clase eventualmente se utilizarán para un nuevo uso.
Después de comprender la asignación de memoria de Java, echemos un vistazo a cómo Java administra la memoria.
Cómo Java maneja la memoria
La gestión de la memoria de Java es el problema de la asignación y la liberación de objetos. En Java, los programadores deben solicitar el espacio de memoria para cada objeto a través de la palabra clave nueva (excepto los tipos básicos), y todos los objetos asignan espacio en el montón (montón). Además, la liberación de objetos está determinada y ejecutada por el GC. En Java, los programas realizan la asignación de memoria, mientras que GC realiza la versión de memoria. Este método de ingresos y gastos en dos líneas simplifica el trabajo de los programadores. Pero al mismo tiempo, también se suma al trabajo del JVM. Esta es también una de las razones por las cuales los programas Java se ejecutan más lentamente. Porque, para liberar correctamente los objetos, GC debe monitorear el estado de ejecución de cada objeto, incluida la aplicación, citas, citas, asignación, etc. del objeto y GC necesita monitorearlo.
Monitorear el estado de un objeto es liberar el objeto con mayor precisión y de manera oportuna, y el principio fundamental de liberar el objeto es que el objeto ya no se hace referencia.
Para comprender mejor cómo funciona GC, podemos considerar el objeto como un vértice de un gráfico dirigido, y la relación de referencia como bordes dirigidos del gráfico, que apunta desde el referente al objeto referenciado. Además, cada objeto de subproceso se puede usar como el vértice inicial de un gráfico. Por ejemplo, la mayoría de los programas comienzan desde el proceso principal, por lo que el gráfico es un árbol de raíz que comienza con el vértice del proceso principal. En este gráfico dirigido, los objetos accesibles por el vértice de la raíz son objetos válidos, y GC no reciclará estos objetos. Si un objeto (subgraph conectado) no se puede obtener de este vértice raíz (tenga en cuenta que el gráfico es un gráfico dirigido), entonces creemos que este objeto (aquellos) ya no se hace referencia y puede ser reciclado por GC.
A continuación, damos un ejemplo de cómo usar gráficos dirigidos para representar la gestión de la memoria. Para cada momento del programa, tenemos un gráfico dirigido que representa la asignación de memoria del JVM. La siguiente imagen es un diagrama del programa en la izquierda que se ejecuta a la línea 6.
Java utiliza gráficos dirigidos para la gestión de la memoria, que pueden eliminar el problema de los bucles de referencia. Por ejemplo, hay tres objetos que se refieren entre sí. Mientras ellos y el proceso de la raíz no sean inalcanzables, GC también puede reciclarlos. La ventaja de este método es que tiene una alta precisión en la gestión de la memoria, pero es de baja eficiencia. Otra tecnología de gestión de memoria comúnmente utilizada es usar contadores. Por ejemplo, el modelo COM utiliza el método contador para administrar componentes. En comparación con los gráficos dirigidos, tiene líneas de baja precisión (es difícil lidiar con problemas de referencia circulares), pero tiene una alta eficiencia de ejecución.
¿Qué es una fuga de memoria en Java?
En Java, las fugas de memoria son la existencia de algunos objetos asignados, que tienen las siguientes dos características. Primero, estos objetos son accesibles, es decir, en el gráfico dirigido, hay rutas que se pueden conectar a ellos; En segundo lugar, estos objetos son inútiles, es decir, el programa no volverá a utilizar estos objetos en el futuro. Si el objeto cumple con estas dos condiciones, se puede determinar que estos objetos son una fuga de memoria en Java, y estos objetos no serán reciclados por GC, pero ocupa la memoria.
En C ++, las filtraciones de memoria tienen un rango más grande. Algunos objetos se asignan espacio de memoria, pero luego inalcanzables. Dado que no hay GC en C ++, esta memoria nunca se recopilará. En Java, estos objetos inalcanzables son reciclados por GC, por lo que los programadores no necesitan considerar esta parte de la fuga de memoria.
A través del análisis, sabemos que para C ++, los programadores necesitan administrar bordes y vértices por sí mismos, mientras que para los programadores de Java, solo necesitan administrar los bordes (no es necesario administrar la liberación de vértices). De esta manera, Java mejora la eficiencia de la programación.
Por lo tanto, a través del análisis anterior, sabemos que también hay fugas de memoria en Java, pero el alcance es más pequeño que el de C ++. Debido a que el lenguaje Java garantiza que cualquier objeto sea accesible, GC gestionan todos los objetos inalcanzables.
Para los programadores, GC es básicamente transparente e invisible. Aunque solo tenemos algunas funciones para acceder a GC, como System.gc (), que ejecuta GC, de acuerdo con la definición de especificación del lenguaje Java, esta función no garantiza que el recolector de basura del JVM ejecute. Porque, los diferentes implementadores de JVM pueden usar diferentes algoritmos para administrar GC. En general, los hilos de GC tienen menor prioridad. Hay muchas estrategias para que JVM llame a GC. Algunos de ellos solo comienzan a trabajar cuando el uso de la memoria alcanza un cierto nivel. Algunos los ejecutan regularmente. Algunos ejecutan GC sin problemas, y algunos ejecutan GC de manera interrumpida. Pero en términos generales, no necesitamos preocuparnos por esto. A menos que en algunas situaciones específicas, la ejecución de GC afecta el rendimiento de la aplicación. Por ejemplo, para los sistemas basados en la web en tiempo real, como los juegos en línea, los usuarios no quieren que GC interrumpa repentinamente la ejecución de la aplicación y realice la recolección de basura, entonces necesitamos ajustar los parámetros de GC para que GC pueda liberar la memoria de manera suave, como descomponer la recolección de basura en una serie de pequeños pasos para ejecutar. El Hotspot JVM proporcionado por Sun admite esta función.
También da un ejemplo típico de fuga de memoria Java.
Vector v = nuevo vector (10); para (int i = 1; i <100; i ++) {objeto o = nuevo objeto (); v.add (o); o = null; }En este ejemplo, aplicamos el ciclo del objeto del objeto y colocamos el objeto aplicado en un vector. Si solo liberamos la referencia en sí, el vector aún hace referencia al objeto, por lo que este objeto no es reciclable para GC. Por lo tanto, si el objeto debe eliminarse del vector después de que se agregue al vector, la forma más fácil es establecer el objeto de vector en nulo.
Fugas de memoria en Java detallado
1. Mecanismo de reciclaje de memoria de Java
Independientemente del método de asignación de memoria de cualquier idioma, es necesario devolver la dirección real de la memoria asignada, es decir, devolver un puntero a la primera dirección del bloque de memoria. Los objetos en Java se crean utilizando métodos nuevos o de reflexión. La creación de estos objetos se asigna en el montón. Todos los objetos son recolectados por la máquina virtual Java a través de un mecanismo de recolección de basura. Para liberar correctamente los objetos, GC monitoreará el estado de salud de cada objeto y monitoreará su aplicación, citas, citas, asignación, etc. Java utilizará métodos gráficos dirigidos para administrar la memoria para monitorear si el objeto se puede lograr en tiempo real. Si no se alcanza, se reciclará, lo que también puede eliminar el problema de los bucles de referencia. En el lenguaje Java, hay dos tipos de espacio de memoria que determina si un espacio de memoria cumple con los criterios de recolección de basura: uno es asignar un valor vacío al objeto, que no se ha llamado a continuación, y el otro es asignar un nuevo valor al objeto, reasignando así el espacio de memoria.
2. Causas de fuga de memoria de Java
La fuga de memoria se refiere al objeto inútil continuo (objeto que ya no se usa) o la memoria de los objetos inútiles no se puede liberar a tiempo, lo que resulta en un desperdicio de espacio de memoria, que se llama filtración de memoria. Las filtraciones de memoria a veces no son graves y no son fáciles de detectar, por lo que los desarrolladores no saben que hay una filtración de memoria, pero a veces puede ser muy grave y le impulsará que salga de la memoria.
¿Cuál es la causa raíz de la fuga de memoria Java? Si un objeto de ciclo de larga vida contiene una referencia a un objeto de ciclo de corta vida, es probable que ocurra una fuga de memoria. Aunque ya no se necesita un objeto de ciclo de corta vida, no se puede reciclar porque mantiene su referencia para un ciclo de larga vida. Este es el escenario en el que se producen fugas de memoria en Java. Hay principalmente las siguientes categorías:
1. La clase de recolección estática causa fuga de memoria:
El uso de hashmap, vector, etc. es más probable que ocurra en fugas de memoria. El ciclo de vida de estas variables estáticas es consistente con el de la aplicación. Todos los objetos a los que hacen referencia no pueden ser liberados porque también serán referenciados por Vector, etc.
Por ejemplo
Vector estático v = nuevo vector (10); para (int i = 1; i <100; i ++) {objeto o = nuevo objeto (); v.add (o); o = null;}En este ejemplo, el objeto se aplica el bucle y el objeto aplicado se coloca en un vector. Si la referencia en sí solo se libera (o = nulo), el vector aún hace referencia al objeto, por lo que este objeto no es reciclable para GC. Por lo tanto, si el objeto debe eliminarse del vector después de que se agregue al vector, la forma más fácil es establecer el objeto de vector en nulo.
2. Cuando se modifican las propiedades del objeto en la colección, el método remove () no funcionará.
Por ejemplo:
public static void main (string [] args) {set <persona> set = new Hashset <PERO> (); persona p1 = nueva persona ("Tang Monk", "Pwd1", 25); persona P2 = nueva persona ("Sun Wukong", "PWD2", 26); Persona P3 = nueva persona ("Zhu (" Zhu Bajie "," pwd3 ", 27); set.add (p1); set.add (p2); set.add (p3); system.out.println (" Hay un total de: "+set.size ()+" elementos! "); // Resultado: hay un total de: 3 elementos! P3.Setage (2); // modifica la edad de P3 y el valor de hashcode correspondiente al elemento P3 cambia en este momento set.remove (p3); // retírelo en este momento, causando una fuga de memoria establecida.Add (P3); // Agregarlo nuevamente y se agregó con éxito System.out.println ("Hay:"+set.size ()+"elementos!"); // Resultado: hay: 4 elementos en total! para (persona persona: set) {system.out.println (persona);}}3. Oyente
En la programación de Java, todos necesitamos tratar con los oyentes. Por lo general, muchos oyentes se usan en una aplicación. Llamaremos a un método de control como AddXXXListener () para agregar oyentes, pero a menudo al liberar el objeto, no recordamos eliminar a estos oyentes, lo que aumenta las posibilidades de filtraciones de memoria.
4. Varias conexiones
Por ejemplo, la conexión de la base de datos (DataSourse.getConnection ()), la conexión de red (Socket) y las conexiones IO no serán recicladas automáticamente por GC a menos que llame explícitamente su método Cerrar () para cerrar su conexión. Los objetos de resultados y de instrucción no se pueden reciclar explícitamente, pero la conexión debe reciclarse explícitamente porque la conexión no se puede reciclar automáticamente en ningún momento. Una vez que la conexión se recicla, los objetos de resultados y de instrucción serán inmediatamente nulos. Sin embargo, si usa un grupo de conexión, la situación es diferente. Además de cerrar explícitamente la conexión, también debe cerrar explícitamente el objeto de la declaración de resultados (cierre uno de ellos, el otro también se cerrará), de lo contrario, no se liberará una gran cantidad de objetos de declaración, causando fugas de memoria. En este caso, la conexión generalmente se lanzará en el intento y finalmente.
5. Referencias a clases internas y módulos externas
Las referencias a clases internas son relativamente fáciles de olvidar, y una vez que no se lanzan, no se pueden lanzar una serie de objetos de clase sucesor. Además, los programadores también deben tener cuidado con las referencias inadvertidas a módulos externos. Por ejemplo, el programador A es responsable del módulo A y llama a un método de módulo B como:
public void registermsg (objeto B);
Este tipo de llamada requiere un gran cuidado. Cuando se pasa un objeto, es muy probable que el módulo B mantenga una referencia al objeto. En este momento, debe prestar atención a si el módulo B proporciona operaciones correspondientes para eliminar las referencias.
6. Modo singleton
El uso incorrecto del patrón Singleton es un problema común que causa fugas de memoria. Los objetos singleton existirán a lo largo de todo el ciclo de vida del JVM después de la inicialización (en forma de variables estáticas). Si el objeto singleton contiene referencias externas, entonces este objeto no será reciclado normalmente por el JVM, lo que resulta en fugas de memoria. Considere el siguiente ejemplo:
Clase A {public A () {B.getInstance (). Seta (this);} ....} // La clase B usa el modo Singleton clase B {private a a a a; privado estática BObviamente, B adopta el patrón Singleton, que contiene una referencia a un objeto A, y el objeto de esta Clase A no se reciclará. Imagine lo que pasaría si A fuera un objeto o tipo de colección más complejo
Resumen de fugas de memoria común en Android
Fuga de la clase de recolección
Si la clase de recolección solo tiene un método para agregar elementos y no tiene un mecanismo de eliminación correspondiente, la memoria estará ocupada. Si esta clase de colección es una variable global (como las propiedades estáticas en la clase, el mapa global, etc., es decir, hay una referencia estática o una señal final todo el tiempo), entonces no hay un mecanismo de deleción correspondiente, lo que puede hacer que la memoria ocupada por la colección solo aumente y no disminuya. Por ejemplo, el ejemplo típico anterior es una de estas situaciones. Por supuesto, definitivamente no escribiremos dicho código 2B en el proyecto, pero aún es fácil suceder si no tenemos cuidado. Por ejemplo, a todos nos gusta hacer algunos cachés a través de Hashmap, por lo que debemos tener más cuidado en esta situación.
Fugas de memoria causada por singletons
Debido a que la naturaleza estática de un singleton hace que su ciclo de vida sea el ciclo de vida de la aplicación, si se usa de manera inapropiada, es fácil causar fugas de memoria. Por ejemplo, el siguiente ejemplo típico,
Public Class AppManager {private static appManager instancia; contexto privado context; privado appManager (contexto context) {this.context = context;} public static appManager getInstance (contexto context) {if (instancia == null) {instancia = new AppManager (context);} return Instance;}}Este es un patrón de singleton normal. Al crear este singleton, dado que se debe pasar un contexto, la duración del ciclo de vida de este contexto es crucial:
1. Si el contexto de la aplicación se pasa en este momento, porque el ciclo de vida de la aplicación es el ciclo de vida de toda la aplicación, no habrá ningún problema.
2. Si el contexto de la actividad se pasa en este momento, cuando la actividad correspondiente a este contexto sale, ya que la referencia al contexto es mantenida por un objeto singleton, su ciclo de vida es igual a todo el ciclo de vida de la aplicación, por lo que cuando la actividad sale, su memoria no se reciclará, lo que causa una fuga.
La forma correcta debe cambiarse a lo siguiente:
Public Class AppManager {private static appManager instancia; contexto privado context; privado appManager (contexto context) {this.context = context.getApplicationContext (); // context utilizando la aplicación} public static appManager getInstance (context) {if (instancia == null) {instancia = nuevo appManager (context);} Instance de retorno;}}O escriba de esta manera, y ni siquiera necesita pasar el contexto en:
Agregue un método estático a su aplicación, GetContext () devuelve el contexto de la aplicación.
...
context = getApplicationContext (); .../*** Get Global Context*@return return Global Context Object*/public static context getContext () {return context;} public class AppManager {private static appManager instancia; contexte privado; private appManager () {this.context = myApplication.getContext (); // context} public appManager getinstance () {) {instancia = new AppManager ();} return instancia;}}Clases internas anónimas/clases internas no estáticas y hilos asincrónicos
Fugas de memoria causada por la creación de instancias estáticas en clases internas no estáticas
A veces podemos comenzar las actividades con frecuencia. Para evitar crear repetidamente los mismos recursos de datos, esta forma de escribir puede ocurrir:
La clase pública MainActivity extiende appCompatactivity {private static testResource mresource = null; @OverrideProtected Void onCreate (Bundle SaveDInStancEstate) {super.oncreate (saveDInstancate); setContentView (r.layout.activity_main); if (mmanager == null) {mmanager = newContView TestResource ();} // ...} clase TestResource {// ...}}Esto crea un singleton de una clase interna no estática dentro de la actividad, y los datos del singleton se usan cada vez que se inicia la actividad. Aunque se evita la creación repetida de recursos, esta escritura causará fugas de memoria, porque la clase interna no estática celebrará referencias a clases externas de forma predeterminada, y la clase interna no estática creará una instancia estática, y el ciclo de vida de la instancia de la instancia, lo que hace que la instancia estática siempre mantenga referencias a la actividad, lo que resulta en los recursos de la memoria de la actividad que no se puede recitar normalmente. La forma correcta de hacerlo es:
Establezca la clase interna como una clase interna estática o extraiga la clase interna y la encapsula en un singleton. Si necesita usar el contexto, siga el contexto recomendado anteriormente para usar la aplicación. Por supuesto, el contexto de la aplicación no es omnipotente, por lo que no se puede usar al azar. En algunos lugares, debe usar el contexto de la actividad. Los escenarios de aplicación del contexto de la aplicación, el servicio y la actividad son los siguientes:
Dónde: NO1 significa que la aplicación y el servicio pueden iniciar una actividad, pero se debe crear una nueva cola de tareas. Para el diálogo, solo se puede crear en actividad
Clase interna anónima
El desarrollo de Android a menudo hereda la implementación de actividad/fragmento/vista. En este momento, si usa clases anónimas y está en manos de hilos asíncronos, tenga cuidado. Si no hay medida, definitivamente conducirá a fugas.
La clase pública MainActivity extiende la actividad {... runnable ref1 = new Myrunable (); runnable ref2 = new Runnable () {@OverridePublic Void run () {}}; ...}La diferencia entre Ref1 y Ref2 es que REF2 usa clases internas anónimas. Echemos un vistazo a la memoria mencionada en el tiempo de ejecución:
Como puede ver, Ref1 no es nada especial.
Pero hay una referencia adicional en el objeto de implementación de la clase anónima Ref2:
Esta referencia de $ 0 apunta a MainActivity. Esta es, es decir, la instancia actual de MainActivity estará celebrada por Ref2. Si esta referencia se pasa a un hilo asincrónico, y este hilo y este ciclo de vida de la actividad son inconsistentes, la fuga de actividad será causada.
Fugas de memoria causada por el controlador
Se debe decir que el problema de fuga de memoria causado por el uso del controlador es el más común. Para evitar ANR, no realizamos operaciones que consumen mucho tiempo en el hilo principal, y usamos el controlador para manejar tareas de red o encapsular algunas devoluciones de llamada de solicitudes y otras API. Sin embargo, el controlador no es omnipotente. Si el código del controlador está escrito de manera estandarizada, puede causar fugas de memoria. Además, sabemos que el manejador, el mensaje y el mensaje están relacionados entre sí. En el caso de que el mensaje enviado por el controlador aún no haya sido procesado, el mensaje y el objeto del controlador que lo envíe estarán en manos del hilo Messagequeue.
Dado que el controlador pertenece a las variables TLS (almacenamiento local de subprocesos), el ciclo de vida y la actividad son inconsistentes. Por lo tanto, este método de implementación generalmente es difícil de garantizar que sea consistente con el ciclo de vida de la vista o la actividad, por lo que es fácil causar la liberación correcta.
Por ejemplo:
SampleActividad de clase pública extiende la actividad {Handler final privado MleAkyHandler = new Handler () {@OverridePublic Void HandLemessage (Mensaje Msg) {// ...}}@overRideProtected void onCreate (Bundle SavedInstanState) {Super.onCreate (SaveDInScate);// Publicar un mensaje y retrasar y retrasar su mensaje y demora en 10. minutos.mleakyhandler.postdelayed (new Runnable () {@OverridePublic Void run () {/ * ... */}}, 1000 * 60 * 10); // Vuelve a la actividad anterior.finish ();}}Se declara una ejecución retrasada de mensaje de mensaje de 10 minutos en la muestra, y MleAkyHandler lo empuja en la cola de mensajes Messagequeue. Cuando la actividad se elimina mediante la final (), el mensaje de que retrasa la ejecución de la tarea continuará existiendo en el hilo principal, que contiene la referencia del controlador de la actividad, por lo que la actividad eliminada por fines () no se reciclará, causando fugas de memoria (porque el controlador no es una clase interna no estática, mantendrá referencias a la clase externa, lo que se refiere a la marea de memoria aquí).
FIJO: Evite usar clases internos no estáticas en la actividad. Por ejemplo, si declaramos el controlador como estático anterior, su período de supervivencia no tiene nada que ver con el ciclo de vida de la actividad. Al mismo tiempo, la actividad se introduce a través de referencias débiles para evitar pasar directamente la actividad como un contexto. Vea el siguiente código:
SampleActividad de la clase pública extiende la actividad {/*** Las instancias de las clases internos estáticas no tienen una referencia implícita*a su clase exterior.*/Clase estática privada myHandler extiende el controlador {privado final de débil <sampleactivity> mActivity; public myHandler (SampleActivity Activity) {Mactividad = NEW DISBILDRELEFER <ampleaCTivity> (Activity);@InterfulePeDEDEDED msg) {SampleActivity Activity = mActivity.get (); if (Activity! = Null) {// ...}}} Private final myHandler mhandler = new MyHandler (this);/*** Instancias de clases anónimas no contiene una referencia implícita*a su clase externa cuando son "estatales".*/Private Static Final Runnable srunnable = new Runnable (new Runnable ()@@)@@Override@@Override {@Override@@Override@@Override {@Override@@Override@@Oul void run () {/ * ... */}};@overrideProtected void onCreate (bundle saveDInStancestate) {super.oncreate (saveDInStancestate; // publique un mensaje y retrase su ejecución durante 10 minutos.mhandler.postdelayed (srunnable, 1000 * 60 * 10);// vaya a la actividad anterior.Descripción general, se recomienda utilizar estática de clase interna + WeakReference. Tenga cuidado de estar vacío antes de cada uso.
Deakreference se mencionó anteriormente, así que aquí hablaré brevemente sobre varios tipos de referencia de objetos Java.
Java tiene cuatro categorías de referencias: referencia sólida, Softreference, WeakReference y Phatomreference.
En el desarrollo de aplicaciones de Android, para evitar el desbordamiento de la memoria, cuando se trata de algunos objetos que ocupan una memoria grande y tienen un ciclo de declaración largo, referencia suave y tecnologías de referencia débiles se pueden usar tanto como sea posible.
Las referencias blandas/débiles se pueden usar junto con una cola de referencia (referencequeue). Si el objeto mencionado por la referencia suave es reciclado por el recolector de basura, la máquina virtual Java agregará la referencia suave a la cola de referencia asociada. Esta cola le permite conocer la lista reciclada de referencias suaves/débiles, limpiando así el búfer que ha fallado referencias blandas/débiles.
Suponga que nuestra aplicación utilizará una gran cantidad de imágenes predeterminadas, como el avatar predeterminado, el icono de juego predeterminado, etc., que se utilizará en muchos lugares. Si lee la imagen cada vez, será más lenta porque la lectura del archivo requiere una operación de hardware, lo que conducirá a un rendimiento más bajo. Por lo tanto, consideramos caché de la imagen y la leemos directamente de la memoria cuando sea necesario. Sin embargo, dado que las imágenes ocupan mucho espacio de memoria y caché, muchas imágenes requieren mucha memoria, es más probable que ocurran excepciones de Memory. En este momento, podemos considerar el uso de técnicas de referencia suaves/débiles para evitar este problema. El siguiente es el prototipo del caché:
Primero defina un hashmap y guarde el objeto de referencia suave.
Mapa privado <String, Softreference <bitmap>> imageCache = new HashMap <String, Softreference <bitmap> ();
Definamos un método para guardar la referencia suave de Bitmap a HASHMAP.
Después de usar referencias suaves, antes de que ocurra la excepción OutOfMemory, se puede liberar el espacio de memoria de estos recursos de imagen en caché, evitando así que la memoria alcance el límite superior y evite el bloqueo.
Si solo desea evitar la ocurrencia de la excepción de OutOfMemory, puede usar referencias suaves. Si le importa más el rendimiento de su aplicación y desea reciclar algunos objetos que ocupan más memoria lo antes posible, puede usar referencias débiles.
Además, puede determinar si el objeto se usa con frecuencia para determinar si se selecciona para referencia suave o referencia débil. Si el objeto se puede usar con frecuencia, intente usar referencias suaves. Si el objeto no se usa más probable, se puede usar con referencias débiles.
Ok, sigue volviendo al tema. Como se mencionó anteriormente, cree una clase interna del controlador estático y use referencias débiles a los objetos sostenidos por el controlador, de modo que los objetos sostenidos por el manejador también se pueden reciclar durante el reciclaje. Sin embargo, aunque esto evita la fuga de actividad, aún puede haber mensajes pendientes en la cola de mensajes del hilo Looper, por lo que debemos eliminar los mensajes en la cola de mensajes Messagequeue durante la destrucción o la parada de la actividad.
Los siguientes métodos pueden eliminar el mensaje:
Public Final Void RemoveCallbacks (Runnable R); Public Final Void RemoveCallbacks (runnable r, token de objeto); Público Final Void RemoveCallBackSandMessages (token de objeto); Public Final Void RemoveMessage (int What); Public Final Void RemoveMessage (int what, objeto objeto);
Intente evitar usar variables de miembros estáticos
Si una variable miembro se declara estática, todos sabemos que su ciclo de vida será el mismo que todo el ciclo de vida del proceso de la aplicación.
Esto causará una serie de problemas. Si el proceso de su aplicación está diseñado para ser residente de la memoria, incluso si la aplicación se corta en segundo plano, esta parte de la memoria no se lanzará. Según el mecanismo de gestión de memoria actual de las aplicaciones móviles, los procesos de fondo que representan una gran cantidad de memoria se reciclarán primero. Si esta aplicación ha realizado la protección mutua de los procesos, hará que la aplicación se reinicie con frecuencia en segundo plano. Cuando el teléfono instala la aplicación que participó en el desarrollo, el teléfono consume energía y tráfico durante la noche, y su aplicación debe estar desinstalada o silenciosa por el usuario.
La solución aquí es:
No inicialice a los miembros estáticos al comienzo de la clase. Se puede considerar la inicialización perezosa.
En el diseño arquitectónico, debemos pensar si es realmente necesario hacer esto e intentar evitarlo. Si la arquitectura debe diseñarse así, entonces tiene la responsabilidad de administrar el ciclo de vida de este objeto.
Evite anular finalizar ()
1. El método finalizar se ejecuta en un momento incierto y no se puede confiar para liberar recursos escasos. Las razones para el tiempo incierto son:
El momento en que la máquina virtual llama a GC es incierto
El momento en que el hilo de demonio finalización está programado es incierto
2. El método finalizar solo se ejecutará una vez. Incluso si el objeto se resucita, si se ha ejecutado el método Finalize, no se ejecutará nuevamente cuando sea GC nuevamente. La razón es:
El objeto que contiene el método Finalize genera una referencia final de la máquina virtual cuando es nuevo, y se hace referencia al objeto. Cuando se ejecuta el método Finalize, se lanzará la referencia final correspondiente al objeto. Incluso si el objeto se resucita en este momento (es decir, hacer referencia al objeto con una referencia sólida), y la segunda vez es GC, ya que la referencia final ya no es correspondiente a él, el método finalizar no se ejecutará.
3. Un objeto que contiene el método finalizar debe pasar por al menos dos rondas de GC antes de que pueda liberarse.
Fugas de memoria causada por el recurso no cerrado
对于使用了BraodcastReceiver,ContentObserver,File,游标Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
一些不良代码造成的内存压力
有些代码并不造成内存泄露,但是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存。
Por ejemplo:
Bitmap 没调用recycle()方法,对于Bitmap 对象在不使用时,我们应该先调用recycle() 释放内存,然后才它设置为null. 因为加载Bitmap 对象的内存空间,一部分是java 的,一部分C 的(因为Bitmap 分配的底层是通过JNI 调用的)。 而这个recyle() 就是针对C 部分的内存释放。
构造Adapter 时,没有使用缓存的convertView ,每次都在创建新的converView。这里推荐使用ViewHolder。
Resumir
对Activity 等组件的引用应该控制在Activity 的生命周期之内; 如果不能就考虑使用getApplicationContext 或者getApplication,以避免Activity 被外部长生命周期的对象引用而泄露。
尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context ),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。
对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
将内部类改为静态内部类
静态内部类中使用弱引用来引用外部类的成员变量
Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空Handler 里面的消息。比如在Activity onStop 或者onDestroy 的时候,取消掉该Handler 对象的Message和Runnable.
在Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为null,比如使用完Bitmap 后先调用recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。
正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。
保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。
The above is a summary of the causes of memory leaks in Java introduced to you by the editor and how to avoid memory leaks (super detailed version). Espero que sea útil para todos. Si tiene alguna pregunta, déjame un mensaje y el editor le responderá a tiempo. ¡Muchas gracias por su apoyo al sitio web de Wulin.com!