Detección y resolución de fugas de memoria de Android

1. Antecedentes:

1.1, ¿qué es una pérdida de memoria?

Una fuga de memoria significa que después de que el programa solicita memoria, no puede liberar el espacio de memoria solicitado, lo que hace que el sistema no pueda recuperar la memoria a tiempo y asignarla a otros procesos para su uso.

1.2, gestión de memoria

49f973de611c49b99b6d44b725df01b3.png

1.3, recolección de basura

 De lo anterior se puede ver que el objeto principal recuperado por GC es el montón de Java, es decir, el nuevo objeto. algoritmo de recolección de basura

 

 algoritmo de barrido de marcas

Pensamiento:

Fase de marcado: marque todos los objetos que necesitan ser reciclados; 

Fase de limpieza: limpieza unificada (reciclado) de todos los objetos marcados

ventaja:

fácil de implementar

defecto:

 Problema de eficiencia: los dos procesos de marcado y limpieza no son eficientes

Problema de espacio: después del barrido de marcas, se generará una gran cantidad de fragmentos de memoria discontinuos

Escenas:

Baja tasa de supervivencia de objetos y baja frecuencia de actividades de recolección de basura (como la generación anterior)

algoritmo de copia

Pensamiento:

Divida la memoria en dos partes de igual tamaño, cada vez que se use una de ellas, cuando la parte de memoria utilizada se agote, copie los objetos sobrevivientes en esta parte de memoria a otra parte de memoria no probada que eventualmente se usará. Esa parte de memoria la memoria se limpia a la vez.

ventaja:

Se resolvió el problema de la baja eficiencia de limpieza en el algoritmo de barrido de marcas: solo se recupera la mitad del área de memoria cada vez

Resuelva el problema de la fragmentación discontinua de la memoria causada por el espacio en el algoritmo de borrado de marcas: mueva los objetos sobrevivientes en la memoria utilizada al puntero en la parte superior de la pila y asigne la memoria en orden

defecto:

Cada vez que la memoria utilizada se reduce a la mitad de la original

Cuando la tasa de supervivencia del objeto es alta, se deben realizar muchas operaciones de copia, es decir, la eficiencia será menor

Escenas:

La supervivencia de los objetos es baja y las áreas que requieren recolección frecuente de basura (como la nueva era)》

Algoritmo de clasificación de marcado

Pensamiento:

Fase de marcado: marque todos los objetos que necesitan ser reciclados;

Fase de finalización: mueve todos los objetos supervivientes hacia uno

Fase de limpieza: limpieza unificada (reciclado) de objetos que no sean el final

ventaja:

Se resolvió el problema de la baja eficiencia de limpieza en el algoritmo de barrido de marcas: despeje el área fuera del final a la vez

Se resolvió el problema de la fragmentación discontinua de la memoria en el espacio en el algoritmo de borrado de marcas: mueva la memoria utilizada en muchos pasos: marque, organice y borre los objetos sobrevivientes al puntero en la parte superior de la pila y asigne la memoria en orden.

defecto:

Muchos pasos: marcar, organizar, borrar

Escenas:

Baja tasa de supervivencia de objetos y baja frecuencia de actividades de recolección de basura (como la generación anterior)

Algoritmo de recolección generacional

Pensamiento:

Divida la memoria del montón de Java en: nueva generación y generación anterior según el ciclo de vida del objeto diferente

Características de cada área:

Generación joven: baja tasa de supervivencia de objetos y alta frecuencia de recolección de basura

Vejez: baja tasa de supervivencia de objetos y frecuencia de comportamiento de recolección de basura en cualquier lugar

De acuerdo con las características de cada área, seleccione el algoritmo de recolección de basura correspondiente (es decir, el algoritmo presentado anteriormente)

La nueva generación: usando el algoritmo de replicación

Generación anterior: use el algoritmo de eliminación de marcas, el algoritmo de clasificación de marcas

ventaja:

Alta eficiencia y alta utilización del espacio: elija diferentes algoritmos de recolección de basura de acuerdo con las características de las diferentes regiones

defecto:

Es necesario mantener varias regiones de almacenamiento dinámico, lo que aumenta la complejidad. Puede ocurrir la promoción de objetos, lo que resulta en una mayor presión de memoria en la generación anterior.

Escenas:

Las máquinas virtuales básicamente usan este algoritmo.

204bbecee1694fb7a81b3c2e49865758.png

1.4. De lo anterior se puede ver que los objetos que se pueden reciclar no causarán basura y no ocuparán una gran cantidad de memoria. Por lo tanto, en la gestión de objetos, debemos prestar atención al hecho de que GC puede reciclar objetos inútiles a tiempo. , de lo contrario, la memoria se llenará gradualmente, lo que finalmente provocará que se agote la memoria, el programa se bloquee y se produzcan excepciones ANR.

En resumen, la razón principal es que los objetos con un ciclo de vida largo se refieren a objetos con un ciclo de vida corto, por lo que los objetos con un ciclo de vida corto no pueden ser liberados y reciclados a tiempo.

Causas comunes de pérdidas de memoria:

Ciclo de vida corto de la actividad de espera única

Las clases internas no estáticas contendrán referencias a las clases externas

Una clase externa contiene un objeto estático de una clase interna no estática

Handler o Runnable como clase interna no estática

Los recursos deben cerrarse y las fugas de memoria causadas por los objetos de colección BroadcastReceiver, ContentObserver, File, Cursor y Bitmap no se limpian a tiempo

2. Herramientas para analizar fugas de memoria

2.1 canario de fugas 

leakcanary es una herramienta para monitorear las fugas de memoria de Android y Java. Puede recopilar dinámicamente pérdidas de memoria en el programa sin afectar el funcionamiento normal del programa.

Sitio web de Github: https://github.com/square/leakcanary

Use, agregue dependencias en la aplicación build.gradle

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:2.10'

Inicializar leakcanary en la aplicación

LeakCanary.Config config = LeakCanary.getConfig().newBuilder()
                .retainedVisibleThreshold(3)
                .computeRetainedHeapSize(false)
                .build();
LeakCanary.setConfig(config);

 2.2 Perfiles de AndroidStudio

5cf2ee2f211e47efbeccff821a3fb1b7.png

 23d25de9d07e40c88d33d396c991424c.png

 Tres, ejemplos, recuerda un problema de fuga de memoria

3.1 Los teléfonos móviles que usamos habitualmente pueden tener un rendimiento relativamente fuerte y no se usarán todo el tiempo. Generalmente, las fugas de memoria no causarán la parálisis de la aplicación. Sin embargo, cuando se encuentre con el problema de fuga de la placa de desarrollo, se convertirá en un error fatal.Debido a la poca memoria y al largo tiempo de ejecución, en caso de una fuga de memoria, la memoria aumentará hasta que se agote y el programa se bloquee. .

3.2 El lugar que causó los problemas anteriores recientemente es un dispositivo de pago con código de escaneo, que enciende la cámara para identificar el código QR, y el subproceso asíncrono escanea el código QR y luego sale de la página de escaneo. La fuga de memoria que finalmente se localizó es causada por la clase de herramienta de administración de páginas ActivityUtils.

3.3 Código fuente que causa pérdida de memoria:

mCamera.setDisplayOrientation(270);//竖屏显示
mCamera.setPreviewDisplay(mHolder);
mCamera.setPreviewCallback(previewCallback);
mCamera.startPreview();
mCamera.autoFocus(autoFocusCallback);

PreviewCallback previewCallback = new PreviewCallback() {
	public void onPreviewFrame(byte[] data, Camera camera) {
		if (data != null) {
			Camera.Parameters parameters = camera.getParameters();
			Size size = parameters.getPreviewSize();//获取预览分辨率

			//创建解码图像,并转换为原始灰度数据,注意图片是被旋转了90度的
			Image source = new Image(size.width, size.height, "Y800");
			//图片旋转了90度,将扫描框的TOP作为left裁剪
			source.setData(data);//填充数据

			ArrayList<HashMap<String, String>> result = new ArrayList<>();
			  //解码,返回值为0代表失败,>0表示成功
			int dataResult = mImageScanner.scanImage(source);
			if (dataResult != 0) {
				playBeepSoundAndVibrate();//解码成功播放提示音
				SymbolSet syms = mImageScanner.getResults();//获取解码结果
				for (Symbol sym : syms) {
					HashMap<String, String> temp = new HashMap<>();
					temp.put(ScanConfig.TYPE, sym.getSymbolName());
					temp.put(ScanConfig.VALUE, sym.getResult());
					result.add(temp);
				}
				if (result.size() > 0) {
					finish();
					EventBusUtil.sendEvent(new Event(EventCode.EVENT_QRCODE, result.get(0).get(ScanConfig.VALUE)));
				} 
			}
			AsyncDecode asyncDecode = new AsyncDecode();
			asyncDecode.execute(source);//调用异步执行解码
		}
	}
};  
private class AsyncDecode extends AsyncTask<Image, Void, ArrayList<HashMap<String, String>>> {
        @Override
        protected ArrayList<HashMap<String, String>> doInBackground(Image... params) {
            final ArrayList<HashMap<String, String>> result = new ArrayList<>();
            Image src_data = params[0];//获取灰度数据
            //解码,返回值为0代表失败,>0表示成功
            final int data = mImageScanner.scanImage(src_data);
            if (data != 0) {
                playBeepSoundAndVibrate();//解码成功播放提示音
                SymbolSet syms = mImageScanner.getResults();//获取解码结果
                for (Symbol sym : syms) {
                    HashMap<String, String> temp = new HashMap<>();
                    temp.put(ScanConfig.TYPE, sym.getSymbolName());
                    temp.put(ScanConfig.VALUE, sym.getResult());
                    result.add(temp);
                    if (!ScanConfig.IDENTIFY_MORE_CODE) {
                        break;
                    }
                }
            }
            return result;
        }

        @Override
        protected void onPostExecute(final ArrayList<HashMap<String, String>> result) {
            super.onPostExecute(result);
            if (!result.isEmpty()) {
                EventBusUtil.sendEvent(new Event(EventCode.EVENT_QRCODE, result.get(0).get(ScanConfig.VALUE)));
                finish();
            } else {
                isRUN.set(false);
            }
        }
    }
ActivityUtil.finishExcept(MainActivity.class);

 3.4 Resultado:

Los resultados del análisis del generador de perfiles muestran un reciclaje frecuente de GC y una fluctuación de memoria severa

5ace84e7195f4c8f852eb488c1398dd2.png

 La memoria pasó de menos de 128 a más de 200 en 5 minutos, casi duplicándose

929712e3bbda4e29886d43ab334a1426.png

 Analizando los resultados, se pueden ver más de 300 fugas de memoria

7052131251b14d1ab6cfd6bd232a63fb.png

 Pérdidas de memoria causadas por tres lugaresd6ab7704ac174e49934b11c1e4125f42.png

La detección de fugas detecta fugas causadas por ActivityUtils

76d9a344cb6a4f19959869bd1f5eb951.jpeg

0a6cdb3ea2d14d028dfccdf4db968d5e.jpeg

bloqueo del programa

 3.5 Análisis de causas

La tarea asincrónica contiene una referencia a la actividad. Una vez que se cierra la actividad pero no se finaliza, la clase de herramienta ActivityUtils no puede finalizar la página final, lo que hace que la página no se recicle, lo que provoca una pérdida de memoria.

3.6 Solución de optimización

El primer paso es optimizar la vista previa del marco de escaneo de la cámara y usar uno almacenado en búfer en su lugar.

mCamera.addCallbackBuffer(new byte[parameters.getPreviewSize().width * parameters.getPreviewSize().height * 3 / 2]); 
mCamera.setPreviewCallbackWithBuffer(previewCallback); 

//Ya sea que haya datos o no, se agregarán a el búfer al final de la vista previa 
PreviewCallback previewCallback = new PreviewCallback() { 
    public void onPreviewFrame(byte[] data, Camera camera) { 
        camera.addCallbackBuffer(data); 
    } 
}
mCamera.setDisplayOrientation(270);//竖屏显示
mCamera.setPreviewDisplay(mHolder);
//mCamera.setPreviewCallback(previewCallback);
mCamera.addCallbackBuffer(new byte[parameters.getPreviewSize().width * parameters.getPreviewSize().height * 3 / 2]);
mCamera.setPreviewCallbackWithBuffer(previewCallback);
mCamera.startPreview();
mCamera.autoFocus(autoFocusCallback);
PreviewCallback previewCallback = new PreviewCallback() {
   public void onPreviewFrame(byte[] data, Camera camera) {    
	if (data != null) {
		if (isRUN.compareAndSet(false, true)) {
			Camera.Parameters parameters = camera.getParameters();
			Size size = parameters.getPreviewSize();//获取预览分辨率

			//创建解码图像,并转换为原始灰度数据,注意图片是被旋转了90度的
			Image source = new Image(size.width, size.height, "Y800");
			//图片旋转了90度,将扫描框的TOP作为left裁剪
			source.setData(data);//填充数据

			ArrayList<HashMap<String, String>> result = new ArrayList<>();
			//解码,返回值为0代表失败,>0表示成功
			int dataResult = mImageScanner.scanImage(source);
			if (dataResult != 0) {
				playBeepSoundAndVibrate();//解码成功播放提示音
				SymbolSet syms = mImageScanner.getResults();//获取解码结果
				for (Symbol sym : syms) {
					HashMap<String, String> temp = new HashMap<>();
					temp.put(ScanConfig.TYPE, sym.getSymbolName());
					temp.put(ScanConfig.VALUE, sym.getResult());
					result.add(temp);
					if (!ScanConfig.IDENTIFY_MORE_CODE) {
						break;
					}
				}
				syms.destroy();
				syms = null;
				if (result.size() > 0) {
					isRUN.set(true);
					finish();
					EventBusUtil.sendEvent(new Event(EventCode.EVENT_QRCODE, result.get(0).get(ScanConfig.VALUE)));
				} else {
					isRUN.set(false);
				}
			} else {
				isRUN.set(false);
			}
		}
	}
    camera.addCallbackBuffer(data);
}

El segundo paso es eliminar el código fuente relacionado con tareas asincrónicas y ActivityUtils

Resultado optimizado:

Se puede ver que la memoria siempre se ha mantenido por debajo de 128M en 30 minutos, y es muy estable

004ef0ccf9fa4da294d0ae619e4bcae9.png

 El resultado final del análisis también es 0 fugas.

448ef4d107ce46fb82c6d743d829bb20.png

 Después de 4 horas de pruebas continuas, la memoria aún está por debajo de 128M, lo cual es una solución perfecta

3.7 Resumen:

  • Cuando se produce una fuga de memoria, primero busque la ubicación de la fuga. Leakcanary y Profiler analizan al mismo tiempo para localizar rápidamente la ubicación.
  • La característica principal de las fugas es que la memoria sigue aumentando y es fácil hacer que los objetos de ciclo de vida largo retengan objetos de ciclo de vida corto, lo que da como resultado objetos de ciclo de vida corto que no se pueden liberar.
  • Por lo tanto, preste atención a este punto en el desarrollo. Cuando finaliza el ciclo de vida corto, primero debe finalizar las tareas del ciclo de vida largo y dejar que los objetos del ciclo de vida largo liberen las referencias del ciclo de vida corto, para que los objetos del ciclo de vida corto puedan ejecutarse con éxito. terminado y reciclado.
  • Para un ciclo de vida largo, use el contexto de la aplicación tanto como sea posible para evitar objetos con un ciclo de vida corto que no se puedan liberar.

Supongo que te gusta

Origin blog.csdn.net/qq_29848853/article/details/129690899
Recomendado
Clasificación