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
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.
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
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
La memoria pasó de menos de 128 a más de 200 en 5 minutos, casi duplicándose
Analizando los resultados, se pueden ver más de 300 fugas de memoria
Pérdidas de memoria causadas por tres lugares
La detección de fugas detecta fugas causadas por ActivityUtils
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
El resultado final del análisis también es 0 fugas.
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.