English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Para las fugas de memoria, en Android, si no se presta atención, es bastante fácil de producir, especialmente en Activity, es bastante común, a continuación, les hablaré de cómo busco fugas de memoria.
Primero, ¿qué es una fuga de memoria?
La fuga de memoria es algunos objetos que ya no se utilizan que aún existen en la memoria y el mecanismo de recolección de basura no puede reciclarlos, lo que los hace permanecer en la memoria, lo que hace que el consumo de memoria sea cada vez mayor, lo que eventualmente lleva a que el rendimiento del programa se degrade.
Dentro del motor de Android, se utiliza el algoritmo de búsqueda de nodos raíz para enumerar nodos raíz para determinar si es basura, el motor de virtualización comenzara a recorrer desde los GC Roots, si un nodo no puede encontrar una ruta que llegue a los GC Roots, es decir, no está conectado a los GC Roots, entonces se demuestra que la referencia es inválida y puede ser reciclada, la fuga de memoria es que algunas malas llamadas hacen que algunos objetos inútiles y GC Roots estén conectados, lo que los hace inaccesibles para la reciclaje.
Ya que sabemos qué es una fuga de memoria, naturalmente sabemos cómo evitarla, es decir, prestar atención a no mantener referencias largas a objetos inútiles al escribir código, aunque suene simple, se necesita suficiente experiencia para lograrlo, por lo que las fugas de memoria son bastante fáciles de producir, y aunque no se pueden evitar completamente, debemos poder encontrar y reparar las fugas de memoria que aparecen en el programa.
Ahora les voy a explicar cómo encontrar fugas de memoria.
Búsqueda de fugas de memoria:
Por ejemplo, el siguiente código:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String string = new String(); } public void click(View view){ Intent intent = new Intent(); intent.setClass(getApplicationContext(), SecondActivity.class); startActivity(intent); } }
public class SecondActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); Runnable runnable = new Runnable() { @Override public void run() { try { Thread.sleep(8000000L); catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(runnable).start(); } }
Cada vez que se saltara a esta Activity se invocara un hilo, luego ese hilo ejecutara el metodo run de runnable. Ya que Runnable es un objeto anonimo interno, posee una referencia a SecondActivity, por lo que dos Activity, pueden ser saltadas desde MainActivity a SecondActivity, a continuación, desde SecondActivity volvemos a MainActivity, y asi sucesivamente5vez, finalmente regresando a MainActivity. Según la lógica, después de regresar de SecondActivity a MainActivity, SecondActivity debería ser destruida y reciclada, pero en la práctica puede no ser así.
En este momento, para determinar si ha ocurrido un desbordamiento de memoria, es necesario usar herramientas. Hay dos formas
1utilizar la herramienta MAT para buscar
Primero, abra la herramienta Android Device Monitor en AS, como se muestra en la siguiente imagen:
Después de abrirlo, se mostrará la siguiente interfaz
Primero, seleccione el paquete de la aplicación que desea probar y luego haga clic en el lugar circunscrito en la imagen, se marcará un icono después del nombre del paquete del programa
Lo que sigue es operar nuestra aplicación para saltar de ida y vuelta5vez.
Luego haga clic en el icono de la siguiente imagen para exportar el archivo hprof para su análisis
El archivo exportado se muestra a continuación:
Después de obtener el archivo hprof, podemos analizarlo utilizando la herramienta MAT.
Abrir la herramienta MAT
Si no tiene, puede descargarlo en el siguiente sitio web
Dirección de descarga de la herramienta MAT
La interfaz se muestra a continuación:
Abrir el archivo hprof exportado anteriormente, y es probable que se informe del siguiente error
Esto se debe a que MAT se utiliza para analizar archivos hprof de programas java, y los archivos hprof exportados por Android tienen una cierta diferencia en el formato, por lo que necesitamos convertir los archivos hprof exportados, la herramienta de conversión proporcionada por sdk es hprof-conv.exe está en la ubicación de la siguiente imagen
A continuación, nos movemos a esta carpeta y ejecutamos el comando para convertir nuestro archivo hprof, como se muestra en la siguiente imagen
Donde hprof-Uso del comando conv, así:
hprof-conv archivo de origen archivo de salida
Por ejemplo, hprof-conv E:\aaa.hprof E:\output.hprof
Es decir, convertir aaa.hprof a output.hprof, el archivo output.hprof es el archivo que hemos convertido, como se muestra en la imagen mat2.hprof es el archivo que hemos convertido.
A continuación, usamos la herramienta MAT para abrir el archivo mat2archivo .hprof, luego abrirlo sin errores, como se muestra en la siguiente imagen:
Luego podemos ver los objetos que existen en la memoria actual, ya que las fugas de memoria generalmente ocurren en Activity, por lo que solo necesitamos buscar Activity.
Hacer clic en el icono QQL marcado en la siguiente imagen e ingresar select * from instanceof android.app.Activity
Similar a una sentencia SQL, encontrar información relacionada con Activity, hacer clic en el signo de exclamación rojo para ejecutar, como se muestra en la siguiente imagen:
接下来 我们就可以看到下面过滤到的Activity信息了
a continuación, podemos ver la información de Activity filtrada a continuación 6como se muestra en la imagen siguiente, aún existe
instancia de SecondActivity, pero queremos salir de todas ellas, lo que indica que ha habido una fuga de memoria
entre ellos, hay dos propiedades: Shallow size y Retained Size Shallow Size el tamaño de la memoria ocupada por el objeto en sí, sin incluir los objetos que él referencia. Para los objetos de tipo no array, su tamaño es la suma del tamaño del objeto y de todos sus variables miembro. por supuesto, también incluirá algunos contenedores de datos de características del lenguaje Java. Para los objetos de tipo array, su tamaño es la suma del tamaño de los objetos de los elementos del array. Retained Size+Retained Size=el tamaño del objeto actual-el tamaño total de los objetos a los que el objeto actual puede referenciar directamente o indirectamente. (El significado de la referencia indirecta: A->B >C, C es la referencia indirecta)
sin embargo, al liberar, también hay que excluir los objetos que son referenciados directamente o indirectamente por GC Roots. No serán considerados como Garbage temporalmente.
a continuación, haga clic derecho en un SecondActivity
seleccionar with all references
abrir la página como se muestra a continuación
ver la página siguientever this0 referenciando estoAticitvythis0 representa el significado de clase interna, es decir, una clase interna hace referencia a Activity y this$0 es referenciado por target. target es un hilo, he encontrado la razón, la causa del derrame de memoria es que Activity es referenciado por la clase interna y la clase interna es utilizada por el hilo, por lo que no se puede liberar. Vamos a ver el código de esta clase.
public class SecondActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); Runnable runnable = new Runnable() { @Override public void run() { try { Thread.sleep(8000000L); catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(runnable).start(); } } De hecho, en
De hecho, en SecondActivity existe un objeto de clase interna Runnable, que luego es utilizado por el hilo, y el hilo debe ejecutar8000 segundos, por lo que el objeto SecondActivity está siendo referenciado y no puede ser liberado, lo que causó la sobrecarga de memoria.
Para resolver este tipo de sobrecarga de memoria, es necesario terminar la hilera a tiempo cuando la Activity sale (aunque no es muy bueno hacerlo), o controlar bien el tiempo de ejecución de la hilera.
De esta manera, hemos encontrado la sobrecarga de memoria en este programa.
2.Utilice directamente el Monitor Memory de Android Studio para encontrar la sobrecarga de memoria
Aún utilizando ese programa, lo explico de manera sencilla.
Primero, ejecute el programa en el teléfono, abra la interfaz de Monitor de AS para ver la imagen de Memory
Haga clic en el ícono del camión pequeño (en el gráfico1El ícono de ubicación) puede desencadenar una vez GC
Haga clic en2El ícono de ubicación puede ver el archivo hprof
A la izquierda están los objetos en la memoria, dentro de los cuales encontrar Activity para ver si existe la Activity que esperamos que ya se haya reciclado. Si aparece la Activity que esperamos que ya se haya reciclado, haga clic en ella para mostrar su número total en la derecha. Al hacer clic en alguna de las de la derecha, se puede mostrar el gráfico de la relación de GC Roots, y al ver el gráfico se puede encontrar la ubicación de la fuga de memoria (similar al primer método)
De esta manera, se ha completado la búsqueda de fugas de memoria.
Las causas de la fuga de memoria en Android se pueden dividir大致 en las siguientes几种 en general:
1.Fuga de memoria causada por la variable estática
Debido a que la vida útil de la variable estática comienza con la carga de la clase y termina con la descarga de la clase, es decir, la variable estática se libera cuando el proceso del programa muere, si se referencia la Activity en la variable estática, esta Activity, debido a que está siendo referenciada, tendrá la misma vida útil que la variable estática, y no podrá ser liberada, causando una fuga de memoria.
Solución:
Cuando la Activity es referenciada por una variable estática, utilice getApplicationContext porque el ciclo de vida de la Application es desde el inicio hasta el final del programa, y es similar a la variable estática.
2.Fuga de memoria causada por la hilera
En situaciones similares a los ejemplos anteriores, el tiempo de ejecución de la hilera es muy largo, incluso si la Activity sale a tiempo, aún se ejecutará, porque la hilera o el Runnable es una clase interna de Activity, por lo que posee una instancia de Activity (porque la creación de una clase interna depende de la clase externa), por lo que la Activity no puede ser liberada.
AsyncTask tiene una piscina de hilos, el problema es más grave.
Solución:
1Planificar razonablemente el tiempo de ejecución de los hilos, controlar que los hilos terminen antes que Activity.
2Cambiar la clase interna a la clase interna estática y usar la referencia débil WeakReference para guardar la instancia de Activity. Porque la referencia débil, una vez que el GC lo encuentre, lo reciclará, por lo que se puede reciclar rápidamente
3La ocupación de demasiado espacio de memoria de BitMap
El análisis de bitmap requiere ocupar memoria, pero la memoria solo proporciona8Asignar el espacio de M a BitMap, si hay demasiadas imágenes y no se recicla el bitmap a tiempo, se producirá un desbordamiento de memoria.
Solución:
Reciclar y comprimir las imágenes a tiempo antes de cargarlas
4La fuga de memoria causada por no cerrar los recursos a tiempo
Por ejemplo, algunos Cursor no se cierran a tiempo, lo que guarda una referencia a Activity, lo que conduce a fugas de memoria
Solución:
En el método onDestory, cerrarlo a tiempo
5La fuga de memoria causada por el uso de .Handler
Debido al uso de Handler, el handler enviará el objeto message al MessageQueue, luego Looper circulará en MessageQueue y extraerá Message para ejecutar. Pero si un Message no se ejecuta por mucho tiempo, debido a que Message tiene una referencia a Handler, y Handler generalmente también es un objeto de clase interna, Message tiene una referencia a Handler, Handler tiene una referencia a Activity, lo que hace que Activity no pueda ser reciclado.
Solución:
Siguiendo el uso de la clase interna estática+La forma de referencia débil puede resolver
Entre algunas de las cuestiones relacionadas con los objetos de conjunto no removidos, los objetos registrados no desregistrados y los problemas de presión de código, también pueden producir fugas de memoria. Pero generalmente, las soluciones mencionadas anteriormente suelen ser solucionables.