누출 감지: Android Studio와 LeakCanary ⚔️

나는 최근에 게시물에서이 댓글을 보았습니다.

The thing really annoying about LeakCanary or Android Studio, most of the time leaks identified by LeakCanary do not appear in Profiler/Memory/memory leaks, I wonder if LeakCanary is showing false positives or Android Studio is missing positives.



좋은 질문입니다. 코드를 자세히 살펴보고 알아봅시다!

Android Studio의 거짓양성 유출



질문에 답하기 전에 거짓 긍정 누출의 아이디어가 어디에서 왔는지에 대해 이야기해야 합니다. 바로 Android Studio입니다.



그 경고는 originally a longer description이었습니다.

Activity and Fragment instances that might be causing memory leaks. For Activities, these are instances that have been destroyed but are still being referenced. For Fragments, these are instances that do not have a valid FragmentManager but are still being referenced. Note, these instance might include Fragments that were created but are not yet being utilized.



documentation은 가양성 누출에 대한 더 많은 정보를 제공합니다.

In certain situations, such as the following, the filter might yield false positives:

  • A Fragment is created but has not yet been used.
  • A Fragment is being cached but not as part of a FragmentTransaction.


구문이 모호하지만 거짓 긍정 누출이 Fragment에만 적용되는 것처럼 보입니다.

Android 스튜디오 누수 필터링



Dump Heap 아이콘을 누르면 Android Studio가 힙을 덤프하고 분석합니다.



누출 인스턴스는 누출 인스턴스만 표시하도록 하단 패널을 업데이트하는 "활동/조각 누출"필터를 활성화하여 표시됩니다. 필터링은 ActivityFragmentLeakInstanceFilter에 의해 수행됩니다.

const val FRAGFMENT_MANAGER_FIELD_NAME = "mFragmentManager"

/**
 * A Fragment instance is determined to be potentially leaked if
 * its mFragmentManager field is null. This indicates that the
 * instance is in its initial state. Note that this can mean that
 * the instance has been destroyed, or just starting to be
 * initialized but before being attached to an activity. The
 * latter gives us false positives, but it should not uncommon
 * as long as users don't create fragments way ahead of the time
 * of adding them to a FragmentManager.
 */
private fun isPotentialFragmentLeak(
  instance: InstanceObject
): Boolean {
  return isValidDepthWithAnyField(
    instance,
    { FRAGFMENT_MANAGER_FIELD_NAME == it },
    { it == null }
  )
}

/**
 * Check if the instance has a valid depth and any field
 * satisfying predicates on its name and value
 */
private fun isValidDepthWithAnyField(
  inst: InstanceObject,
  onName: (String) -> Boolean,
  onVal: (Any?) -> Boolean
): Boolean {
  val depth = inst.depth
  return depth != 0 && depth != Int.MAX_VALUE &&
         inst.fields.any {
           onName(it.fieldName) && onVal(it.value)
         }
}


(예, Fragfment 관리자)

따라서 mFragmentManager 필드가 null이고 강력한 참조를 통해 조각에 도달할 수 있는 경우 조각이 누출되는 것으로 간주됩니다(위 코드에서 유효한 깊이가 의미함). 프래그먼트 인스턴스를 생성했지만 추가하지 않으면 누수로 보고되므로 오탐지 누수에 대한 경고가 표시됩니다.

LeakCanary가 조각 누출을 찾는 방법



Android Studio와 달리 LeakCanary는 메모리의 모든 조각 인스턴스에 대해 mFragmentManager 필드를 확인하지 않습니다. LeakCanary는 Android 수명 주기에 연결되어 조각이 파괴되고 가비지 수집되어야 할 때를 자동으로 감지합니다. 이러한 파괴된 객체는 ObjectWatcher 로 전달되어 weak references 을 보유합니다.

if (activity is FragmentActivity) {
  val supportFragmentManager = activity.supportFragmentManager
  supportFragmentManager.registerFragmentLifecycleCallbacks(
    object : FragmentManager.FragmentLifecycleCallbacks() {

      override fun onFragmentDestroyed(
        fm: FragmentManager,
        fragment: Fragment
      ) {
        objectWatcher.watch(
          fragment,
          "Received Fragment#onDestroy() callback"
        )
      }
    },
    true
  ) 
}

ObjectWatcher에서 보유하고 있는 약한 참조가 5초 동안 대기하고 가비지 수집을 실행한 후에도 지워지지 않으면 감시 객체가 유지되고 잠재적으로 누수되는 것으로 간주됩니다. LeakCanary는 Java 힙을 .hprof 파일(힙 덤프)로 덤프합니다.



그런 다음 LeakCanary는 Shark을 사용하여 .hprof 파일을 구문 분석하고 해당 힙 덤프에서 보유된 개체를 찾습니다.



LeakCanary가 Android Studio와 다른 점은 LeakCanary가 생성한 맞춤 약한 참조( KeyedWeakReference )를 찾고 KeyedWeakReferenceFinder에서 해당 referent 필드를 확인하는 것입니다.

/**
 * Finds all objects tracked by a KeyedWeakReference, i.e. al
 * objects that were passed to ObjectWatcher.watch().
 */
object KeyedWeakReferenceFinder : LeakingObjectFinder {

  override fun findLeakingObjectIds(graph: HeapGraph): Set<Long> {
    return graph.findClassByName("leakcanary.KeyedWeakReference")
      .instances
      .map { weakRef ->
        weakRef["java.lang.ref.Reference", "referent"]!!
          .value
          .asObjectId
      }
      .filter { objectId ->
        objectId != ValueHolder.NULL_REFERENCE
      }
      .toSet()
  }
}


즉, LeakCanary는 실제로 누출되는 조각만 표시합니다.

LeakCanary가 더 많은 유출을 보고하는 이유



이제 우리는 LeakCanary가 Android Studio와 동일한 거짓 긍정 누출이 없음을 확인했으므로 기본 관찰로 돌아가 보겠습니다.

most of the time leaks identified by LeakCanary do not appear in Profiler/Memory/memory leaks



설명은 LeakCanary documentation에 있습니다.

LeakCanary automatically detects leaks for the following objects:

  • destroyed Activity instances
  • destroyed Fragment instances
  • destroyed fragment View instances
  • cleared ViewModel instances


Android Studio는 파괴된 조각View 인스턴스 또는 지워진ViewModel 인스턴스에 대한 누수를 감지하지 않습니다. 전자는 실제로 common cause of leak입니다.

Adding a Fragment instance to the backstack without clearing that Fragment’s view fields in Fragment.onDestroyView() (more details in this StackOverflow answer).



결론



LeakCanary가 누출 추적을 보고하면 확실히 누출이 있는 것입니다. LeakCanary는 항상 옳습니다! 안타깝게도 누수를 수정하지 않은 것에 대한 핑계로 작은 노란 새의 오탐지 누수를 탓할 수는 없습니다.

좋은 웹페이지 즐겨찾기