Análisis del principio del código fuente de Android memory leak-leak

Análisis del principio de fuga

Introducción

El uso de MAT para analizar problemas de memoria tiene algunos umbrales, algunas dificultades y la eficiencia no es muy alta. Para un problema de pérdida de memoria, pueden ser necesarias varias investigaciones y comparaciones para encontrar la causa del problema. Para encontrar fugas de memoria rápida y fácilmente, Square tiene LeakCanary de código abierto basado en MAT

En resumen, LeakCanary es una herramienta eficaz, simple y fácil de usar para detectar fugas de memoria basada en MAT.

insuficiente

Problemas de OOM causados ​​por la solicitud de memoria de gran capacidad, la memoria de mapa de bits que no se libera, las pérdidas de memoria en el servicio no se pueden detectar, etc. Necesitamos usar Mat.

Fugas de memoria comunes

No lo abriré por el momento, y luego haré esta introducción. De hecho, resumí las
fugas de memoria y las soluciones que son similares a las excepciones menores . 1
Fugas de memoria y soluciones comunes 2

Principio fundamental

La clase SoftReference, la clase WeakReference y la clase PhantomReference representan referencias suaves, referencias débiles y referencias fantasma, respectivamente. La clase ReferenceQueue representa una cola de referencia. Se puede usar junto con estas tres clases de referencia. Por ejemplo, si el objeto al que hace referencia una referencia débil se recolecta como basura, la máquina virtual Java colocará la referencia débil en la ReferenceQueue asociada con ella, por lo que Si revisamos la cola cuando el objeto debe ser reciclado, si se encuentra que existe este objeto en la cola, si la aplicación indica que el objeto ha sido reciclado normalmente, si la detección es que no hay una referencia débil al objeto, no significa que el objeto no se ha reciclado normalmente, significa una pérdida de memoria. . Pero en la operación real, cuando pensamos que el objeto debe reciclarse, es posible que haya un cierto retraso o que el GC no ocurra, por lo que debemos darle un cierto tiempo de recolección y reintentarlo o activar manualmente el GC (no necesariamente ejecutado).

utilizar

Después de 2.0, solo necesita agregar esto en gradle. 2.4 está escrito en kotlin.

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'

El trabajo de registrar la instalación se coloca en AppWatcherInstaller.

La clase AppWatcherInstaller hereda de ContentProvider, uno de los cuatro componentes principales. AppWatcherInstaller $ MainProcess se ha registrado
en fugacanary / fugacanary-object-watcher-android / src / main / AndroidManifest.xml en el submódulo fugacanary-object-watcher-android
. MainProcess es una subclase de AppWatcherInstaller. AppWatcherInstaller es una clase sellada.

Características de sellado:

  • Declare una clase sellada para usar el modificador sellado en el nombre de la clase
  • Todas las subclases deben declararse en el mismo archivo que la propia clase sellada (las clases extendidas de subclases no están controladas por esto)
  • Una clase sellada es abstracta por sí misma, no se puede instanciar directamente y puede tener miembros abstractos
  • Las clases selladas no pueden tener constructores no privados (su constructor predeterminado es privado)
/**
 * Content providers are loaded before the application class is created. [AppWatcherInstaller] is
 * used to install [leakcanary.AppWatcher] on application start.
 */

internal sealed class AppWatcherInstaller : ContentProvider() {
	...省略代吗
  /**
   * [MainProcess] automatically sets up the LeakCanary code that runs in the main app process.
   */
  internal class MainProcess : AppWatcherInstaller()

...省略代吗
}
//注册
<provider
    android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
    android:authorities="${applicationId}.leakcanary-installer"     	
    android:enabled="@bool/leak_canary_watcher_auto_install"
    android:exported="false"/>

La característica de ContentProvider es que no es necesario mostrar la inicialización de la llamada, solo necesita estar registrado en AndroidManifest.xml, y se llamará al método onCreate () de ContentProvider antes de que se ejecute onCreate de la aplicación. Es precisamente para aprovechar esto que LeakCanary escribe el registro en él, y el sistema lo llama automáticamente para completarlo, sin que el desarrollador lo sepa.
También se puede ver aquí que comprendemos la importancia del proceso de inicio de Android de los cuatro códigos fuente de los componentes principales. Podemos hacer muchas optimizaciones.

principio

Cuando detectar fugas

En primer lugar, primero debemos entender que LeakCanary-Android detecta principalmente fugas de memoria en Actividad y Fragmento. Las fugas de otros objetos debemos hacerlas nosotros mismos.
Echemos un vistazo a su código fuente registrado con preguntas.

Rastree el código registrado en AppWatcherInstaller a AppWatcher

 AppWatcher.manualInstall(application)
object AppWatcher {

  .....省略代码
  /**
   * [AppWatcher] is automatically installed in the main process on startup. You can
   * disable this behavior by overriding the `leak_canary_watcher_auto_install` boolean resource:
   *
   * ```
   * <?xml version="1.0" encoding="utf-8"?>
   * <resources>
   *   <bool name="leak_canary_watcher_auto_install">false</bool>
   * </resources>
   * ```
   *
   * If you disabled automatic install then you can call this method to install [AppWatcher].
   */
  fun manualInstall(application: Application) {
    InternalAppWatcher.install(application)
  }
}

Continúe rastreando el código InternalAppWatcher.install (aplicación) a InternalAppWatcher

/**
 * Note: this object must load fine in a JUnit environment
 */
internal object InternalAppWatcher {
	...省略代吗
  fun install(application: Application) {
  ...省略代吗
    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
    FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
   ...省略代吗
  }
  ...省略代吗
}

InternalAppWatcher hizo ActivityDestroyWatcher.install
FragmentDestroyWatcher.install
Seguimos rastreando los detalles del código de FragmentDestroyWatcher y ActivityDestroyWatcher.

internal class ActivityDestroyWatcher private constructor(
  private val objectWatcher: ObjectWatcher,
  private val configProvider: () -> Config
) {
//创建Activity生命周期监听对象 监听Activity destroy方法然后调用 objectWatcher
  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
      //判断config中配置是否监听activity泄漏
        if (configProvider().watchActivities) {
          objectWatcher.watch(
              activity, "${activity::class.java.name} received Activity#onDestroy() callback"
          )
        }
      }
    }

  companion object {
  //伴生对象中install方法中创建ActivityDestroyWatcher对象并注册Activiy监听传入ObjectWatcher ,Config
    fun install(
      application: Application,
      objectWatcher: ObjectWatcher,
      configProvider: () -> Config
    ) {
      val activityDestroyWatcher =
        ActivityDestroyWatcher(objectWatcher, configProvider)
    application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
    }
  }
}

Fragment es más complicado. En la instalación en FragmentDestroyWatcher, FragmentManager se obtiene a través de Activity en el oyente onCreate iniciado por Activity, y el oyente se registra a través de FragmentManager.registerFragmentLifecycleCallbacks, principalmente en los métodos onFragmentViewDestroyed y onFragmentDestroyed. .

Entre ellos, las bibliotecas Fragment en AndroidO y AndroidX y AndroidSupport están adaptadas:
AndroidOFragmentDestroyWatcher AndroidXFragmentDestroyWatcher
AndroidSupportFragmentDestroyWatcher
El
código específico no aparece aquí. Puedes importar el leakingCanary tú mismo para ver el código específicamente.

Cómo detectar fugas

A través del análisis anterior, encontramos que tanto Fragment como Activity monitorean el método de destrucción y luego ejecutan el método de observación de ObjectWatcher. Parece que el código de detección del núcleo está adentro. Entonces, explorémoslo ahora.

 objectWatcher.watch(
            view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
            "(references to its views should be cleared to prevent leaks)"
        )

Rastrea el método objectWatcher.watch.
Durante el proceso de seguimiento, es posible que deba comprender los cuatro tipos de referencias, fuerte, débil y débil,
que contiene una de mis preguntas: ¿Por qué la Actividad o la Aplicación están envueltas por referencias débiles y no se reciclarán durante la GC?
Es más común en Android. A menudo nos encontramos con la necesidad de usar Contexto y sus subclases, como Aplicación, Actividad y otras clases de componentes. En este momento, si se usan referencias sólidas, es probable que ocurran pérdidas de memoria y el uso de referencias suaves causará problemas. Los recursos necesarios se desperdician, por lo que usar referencias débiles es la mejor solución en este momento, porque los componentes con sus propios ciclos de vida como Activity están fuertemente referenciados por objetos de nivel inferior, como AM (ActivityManager), por lo que Cuando este tipo de objeto todavía está en su ciclo de vida, no se reciclará fácilmente. Solo después de que termine el ciclo de vida, el CG lo considerará que solo tiene referencias débiles y, finalmente, se reciclará.

Es decir, cuando un objeto es referenciado por referencias fuertes y débiles al mismo tiempo, no se reciclará durante la GC. Solo después del final de su ciclo de vida, la GC lo determinará como solo referencias débiles y eventualmente se reciclará.

class ObjectWatcher constructor(
  private val clock: Clock,
  private val checkRetainedExecutor: Executor,
  /**
   * Calls to [watch] will be ignored when [isEnabled] returns false
   */
  private val isEnabled: () -> Boolean = { true }
) {
	private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()

 	private val queue = ReferenceQueue<Any>()

 @Synchronized fun watch(watchedObject: Any) {
    watch(watchedObject, "")
  }

 /**
   * Watches the provided [watchedObject].
   *
   * @param description Describes why the object is watched.
   */
  @Synchronized fun watch(
    watchedObject: Any,
    description: String
  ) {
    if (!isEnabled()) {
      return
    }
    //先清除一次防止重复添加(应该是这么个作用因为key每次都是随机生成的)
    removeWeaklyReachableObjects()
	//生成每个被观察对象的key
    val key = UUID.randomUUID()
        .toString()
    //主要是在dump的时候使用计算一个对象被引用了多久
    val watchUptimeMillis = clock.uptimeMillis()
    //KeyWeakReference是WeakRefrence的子类,LeakCanary的原理就是如果对象被回收了的话
   //会把引用对象放到 queue(ReferenceQueue<Any>) 生成一个对watchedObject的弱引用
    val reference =
      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    SharkLog.d {
      "Watching " +
          (if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
          (if (description.isNotEmpty()) " ($description)" else "") +
          " with key $key"
    }
	//把相应的refrece放到被观察对象map中,watchedObjects可变map
    watchedObjects[key] = reference
    //然后通过Handler发送延迟五秒消息去判断是否还有引用没回收并dump,下边会介绍checkRetainedExecutor对象的定义
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }
  
//poll出queue中的被回收的对象移除watchedObjects中的相应弱引用对象
private fun removeWeaklyReachableObjects() {
    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
    // reachable. This is before finalization or garbage collection has actually happened.
    var ref: KeyedWeakReference?
    do {
      ref = queue.poll() as KeyedWeakReference?
      if (ref != null) {
        watchedObjects.remove(ref.key)
      }
    } while (ref != null)
  }
  
@Synchronized fun addOnObjectRetainedListener(listener: OnObjectRetainedListener) {
    onObjectRetainedListeners.add(listener)
  }

  
//handler延迟五秒后执行run方法中的moveToRetained方法
@Synchronized private fun moveToRetained(key: String) {
	//先去poll出queue中的被回收的对象移除watchedObjects中的相应弱引用对象 防止5秒过程中可能对象已经被回收了 下边还要进行一次gc和dump
    removeWeaklyReachableObjects()
    //拿出当前watch对象的弱引用
    val retainedRef = watchedObjects[key]
    //【标记最后一步】若该对象若引用不为空则继续下一步为null则认为已被回收了不作处理 
    if (retainedRef != null) {
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      //执行onObjectRetainedListeners中每个监听的onObjectRetained方法
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
  }
}

CheckRetainedExecutor y la detección ObjectWatch de Activity y Fragment
se definen en InternalAppWatcher. Mencioné esta clase antes.

private val checkRetainedExecutor = Executor {
  	//AppWatcher.config.watchDurationMillis
	//在AppWatcher的Config类中定义val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5),默认五秒
    mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
}
  
val objectWatcher = ObjectWatcher(
      clock = clock,
      checkRetainedExecutor = checkRetainedExecutor,
      isEnabled = { true }
)

Luego, el último paso del análisis de código (el objeto de referencia no es nulo después de que se borra la información de cinco segundos del controlador marcado con [Marcar el último paso]),
rastreamos directamente el código addOnObjectRetainedListener en ObjectWatcher

@Synchronized fun addOnObjectRetainedListener(listener: OnObjectRetainedListener) {
    onObjectRetainedListeners.add(listener)
  }

Coloque el mouse sobre este método y haga clic en Command + touchpad, encontraremos que estamos en el método de invocación de InternalLeakCanary, pero este lugar lo hemos analizado desde el frente y no encontramos ninguna llamada allí.

override fun invoke(application: Application) {
    AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
    val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
    //创建一个后台子线程的Handler,backgroundHandler这个后边是用来检测未回收对象的
    val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
    handlerThread.start()
    val backgroundHandler = Handler(handlerThread.looper)

    heapDumpTrigger = HeapDumpTrigger(
        application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
        configProvider
    )
  }

Pero no se preocupe dónde debe llamarse. Cuando lo miramos, prestamos atención al proceso principal. Ignore esto. Está bien. Regresemos y veamos dónde se usa. Después del AppWatcherInstaller, AppWatcher, InternalAppWatcher,
finalmente estamos en InternalAppWatcher. Descubrí que fue llamado a través de la reflexión

internal object InternalAppWatcher {
 private val onAppWatcherInstalled: (Application) -> Unit
  ...省略代吗
  init {
  //我们创建InternalAppWatcher时init方法就会调用,onAppWatcherInstalled是kotlin中invoke约定的应用可以将invoke函数的lambda表达式赋值给一个变量,
Kotlin的约定有很多种,而比如使用便捷的get操作,以及重载运算符等等,invoke约定也仅仅是一种约定而已;
我们可以把lambda表达式或者函数直接保存在一个变量中,然后就像执行函数一样直接执行这个变量,
这样的变量通常声明的时候都被我们赋值了已经直接定义好的lambda,或者通过成员引用而获取到的函数;
但是别忘了,在面向对象编程中,一个对象在通常情况下都有自己对应的类,那我们能不能定义一个类,
然后通过构造方法来产生一个对象,然后直接执行它呢?这正是invoke约定发挥作用的地方。
我们只需要在一个类中使用operator来修饰invoke函数,这样的类的对象就可以直接像一个保存lambda表达式的变量一样直接调用,而调用后执行的函数就是invoke函数。
我们还有另一种方式来实现可调用的对象,即让类继承自函数类型,然后重写invoke方法。
    val internalLeakCanary = try {
      val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
      leakCanaryListener.getDeclaredField("INSTANCE")
          .get(null)
    } catch (ignored: Throwable) {
      NoLeakCanary
    }
    @kotlin.Suppress("UNCHECKED_CAST")
    onAppWatcherInstalled = internalLeakCanary as (Application) -> Unit
  }

  fun install(application: Application) {
   ...省略代吗
    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
    FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
    
    //在install的时候通过onAppWatcherInstalled调用InternalLeakCanary invoke方法
    onAppWatcherInstalled(application)
  }
...省略代吗
}

Una introducción detallada a kotlinDSL y convenciones está aquí

El bloque de código de inicio y el método de construcción en Kotlin y la secuencia de tiempo y ejecución del código en el objeto complementario

Bueno, después de que lo anterior se haya dividido, ya sabemos dónde InternalLeakCanary crea y registra el objeto de escucha no recuperado (addOnObjectRetainedListener).
A continuación, analizamos lo que se llama en el método después de que InternalLeakCanary implementa el método de monitoreo.

override fun onObjectRetained() {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.onObjectRetained()
    }
  }

  fun onDumpHeapReceived(forceDump: Boolean) {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.onDumpHeapReceived(forceDump)
    }
  }

continuar siguiendo

//调用
heapDumpTrigger.onObjectRetained()

//onObjectRetained方法的实现
 fun onObjectRetained() {
    scheduleRetainedObjectCheck(
        reason = "found new object retained",
        rescheduling = false
    )
  }

分析 scheduleRetainedObjectCheck :

private fun scheduleRetainedObjectCheck(
    reason: String,
    rescheduling: Boolean,
    delayMillis: Long = 0L
  ) {
    val checkCurrentlyScheduledAt = checkScheduledAt
    if (checkCurrentlyScheduledAt > 0) {
      val scheduledIn = checkCurrentlyScheduledAt - SystemClock.uptimeMillis()
      SharkLog.d { "Ignoring request to check for retained objects ($reason), already scheduled in ${scheduledIn}ms" }
      return
    } else {
      val verb = if (rescheduling) "Rescheduling" else "Scheduling"
      val delay = if (delayMillis > 0) " in ${delayMillis}ms" else ""
      SharkLog.d { "$verb check for retained objects${delay} because $reason" }
    }
    checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
    //在后台子线程的handler中检测未回收对象
    backgroundHandler.postDelayed({
      checkScheduledAt = 0
      checkRetainedObjects(reason)
    }, delayMillis)
  }

Luego analice checkRetainedObjects:

 private fun checkRetainedObjects(reason: String) {
    val config = configProvider()
    // A tick will be rescheduled when this is turned back on.
    //配置中是否要dump堆栈信息并通知
    if (!config.dumpHeap) {
      SharkLog.d { "Ignoring check for retained objects scheduled because $reason: LeakCanary.Config.dumpHeap is false" }
      return
    }
	//目前还有多少未回收对象
    var retainedReferenceCount = objectWatcher.retainedObjectCount

	//如果还有未回收对象则在进行一次GC不一定能执行确保对象能回收的都回收了,然后再去赋值retainedReferenceCount
    if (retainedReferenceCount > 0) {
      gcTrigger.runGc()
      retainedReferenceCount = objectWatcher.retainedObjectCount
    }
	//检测retainedReferenceCount是否为零或者小于retainedVisibleThreshold = 5如果小于则跳过下边的检测延迟 WAIT_FOR_OBJECT_THRESHOLD_MILLIS = 2_000L 2秒 重新从scheduleRetainedObjectCheck重新开始检测,一直循环,直到大于等于5个或者等于0个,为了防止频发回收堆造成卡顿。为零的话就直接显示通知。
    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
	//大于5个后,如果处于debug模式,会再等20秒,再次执行scheduleRetainedObjectCheck操作。防止debug模式会减慢回收
    if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
      onRetainInstanceListener.onEvent(DebuggerIsAttached)
      showRetainedCountNotification(
          objectCount = retainedReferenceCount,
          contentText = application.getString(
              R.string.leak_canary_notification_retained_debugger_attached
          )
      )
      scheduleRetainedObjectCheck(
          reason = "debugger is attached",
          rescheduling = true,
          delayMillis = WAIT_FOR_DEBUG_MILLIS
      )
      return
    }

    val now = SystemClock.uptimeMillis()
    val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
    //距离上次dumpheap的时间小于WAIT_BETWEEN_HEAP_DUMPS_MILLIS =60_000L 1分钟 则继续去检测未回收对象直到大于一分钟
    if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
      onRetainInstanceListener.onEvent(DumpHappenedRecently)
      showRetainedCountNotification(
          objectCount = retainedReferenceCount,
          contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
      )
      scheduleRetainedObjectCheck(
          reason = "previous heap dump was ${elapsedSinceLastDumpMillis}ms ago (< ${WAIT_BETWEEN_HEAP_DUMPS_MILLIS}ms)",
          rescheduling = true,
          delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
      )
      return
    }

    SharkLog.d { "Check for retained objects found $retainedReferenceCount objects, dumping the heap" }
    //显示未回收对象个数通知隐藏
    dismissRetainedCountNotification()
    //dump堆栈信息
    dumpHeap(retainedReferenceCount, retry = true)
  }

El resumen es:
1) Si el número de objetos no recuperados es menor que 5, espere 5 segundos sin realizar ninguna operación para verificar el número no recuperado nuevamente, y repita hasta que sea mayor o igual a 5 o igual a 0, para evitar la recuperación frecuente del montón. Caton.
2) Después de más de 5, si está en modo de depuración, esperará otros 20 segundos y ejecutará nuevamente la operación de detección scheduleRetainedObjectCheck. Evitar que el modo de depuración ralentice la recuperación
3) Si el último análisis de la pila es mayor o igual a 1 minuto, si no es más de un minuto, es necesario retrasarlo nuevamente (1 minuto, la hora actual desde la última vez) para realizar la operación de detección ScheduleRetainedObjectCheck nuevamente

6. Si se cumplen las condiciones anteriores, puede iniciar el análisis de la pila
1), obtener el archivo de contenido Debug.dumpHprofData (heapDumpFile.absolutePath)
2), objectWatcher.clearObjectsWatchedBefore (heapDumpUptimeMillis) Esta operación es para eliminar lo previamente analizado El objeto, es decir, eliminar el objeto que no se recicló antes y no está dentro del alcance de este análisis
3), HeapAnalyzerService abre el servicio IntentService para el análisis
4), inserta el resultado en la base de datos (la fuga distingue la fuga de memoria de la aplicación en sí y la biblioteca de clases Fuga de memoria) y enviar notificación

Bueno, aquí está el principio y el análisis del código fuente de LeakCanary

La versión antigua del principio LeakCanary ¡El análisis de la
versión antigua del principio LeakCanary es muy sencillo de entender!

Supongo que te gusta

Origin blog.csdn.net/u011148116/article/details/106762665
Recomendado
Clasificación