main

square/leakcanary

Last updated at: 29/12/2023 09:38

AndroidNativeSizeMapper.kt

TLDR

This file contains the implementation of the AndroidNativeSizeMapper class, which is responsible for mapping object IDs to native sizes tracked by NativeAllocationRegistry$CleanerThunk. It provides the mapNativeSizes method to retrieve a map of object IDs and their corresponding native sizes.

Methods

mapNativeSizes

This method returns a map of object IDs to native sizes. It uses the graph property of the AndroidNativeSizeMapper instance to locate instances of the sun.misc.Cleaner class and extract the necessary information to calculate the native size. The size is determined by accessing the size field of the libcore.util.NativeAllocationRegistry instance referenced by the CleanerThunk. The calculated native sizes are then stored in a map and returned.

Classes

AndroidNativeSizeMapper

The AndroidNativeSizeMapper class is responsible for mapping object IDs to native sizes. It has a constructor that takes a HeapGraph object and a mapNativeSizes method to retrieve the mapping.

Companion Object

The companion object of the AndroidNativeSizeMapper class provides a static mapNativeSizes method that takes a HeapGraph object and returns the mapped native sizes.

package shark

import shark.HeapObject.HeapInstance

class AndroidNativeSizeMapper(private val graph: HeapGraph) {

  /**
   * Returns a map of Object id to native size as tracked by NativeAllocationRegistry$CleanerThunk
   */
  fun mapNativeSizes(): Map<Long, Int> {
    return graph.context.getOrPut("AndroidNativeSizeMapper") {
      buildNativeSizeMap()
    }
  }

  private fun buildNativeSizeMap(): Map<Long, Int> {
    val nativeSizes = mutableMapOf<Long, Int>()
    // Doc from perflib:
    // Native allocations can be identified by looking at instances of
    // libcore.util.NativeAllocationRegistry$CleanerThunk. The "owning" Java object is the
    // "referent" field of the "sun.misc.Cleaner" instance with a hard reference to the
    // CleanerThunk.
    //
    // The size is in the 'size' field of the libcore.util.NativeAllocationRegistry instance
    // that the CleanerThunk has a pointer to. The native pointer is in the 'nativePtr' field of
    // the CleanerThunk. The hprof does not include the native bytes pointed to.
    graph.findClassByName("sun.misc.Cleaner")?.let { cleanerClass ->
      cleanerClass.directInstances.forEach { cleaner ->
        val thunkField = cleaner["sun.misc.Cleaner", "thunk"]
        val thunkId = thunkField?.value?.asNonNullObjectId
        val referentId =
          cleaner["java.lang.ref.Reference", "referent"]?.value?.asNonNullObjectId
        if (thunkId != null && referentId != null) {
          val thunkRecord = thunkField.value.asObject
          if (thunkRecord is HeapInstance && thunkRecord instanceOf "libcore.util.NativeAllocationRegistry\$CleanerThunk") {
            val allocationRegistryIdField =
              thunkRecord["libcore.util.NativeAllocationRegistry\$CleanerThunk", "this\$0"]
            if (allocationRegistryIdField != null && allocationRegistryIdField.value.isNonNullReference) {
              val allocationRegistryRecord = allocationRegistryIdField.value.asObject
              if (allocationRegistryRecord is HeapInstance && allocationRegistryRecord instanceOf "libcore.util.NativeAllocationRegistry") {
                var nativeSize = nativeSizes[referentId] ?: 0
                nativeSize += allocationRegistryRecord["libcore.util.NativeAllocationRegistry", "size"]?.value?.asLong?.toInt()
                  ?: 0
                nativeSizes[referentId] = nativeSize
              }
            }
          }
        }
      }
    }
    return nativeSizes
  }

  companion object {
    fun mapNativeSizes(heapGraph: HeapGraph): Map<Long, Int> {
      return AndroidNativeSizeMapper(heapGraph).mapNativeSizes()
    }
  }
}