All Downloads are FREE. Search and download functionalities are using the official Maven repository.

kshark.internal.ObjectDominators.kt Maven / Gradle / Ivy

package kshark.internal

import kshark.GcRoot.ThreadObject
import kshark.HeapGraph
import kshark.HeapObject.HeapClass
import kshark.HeapObject.HeapInstance
import kshark.HeapObject.HeapObjectArray
import kshark.HeapObject.HeapPrimitiveArray
import kshark.IgnoredReferenceMatcher
import kshark.OnAnalysisProgressListener
import kshark.ValueHolder

/**
 * Exposes high level APIs to compute and render a dominator tree. This class
 * needs to be public to be used by other LeakCanary modules but is internal and
 * its API might change at any moment.
 *
 * Note that the exposed APIs are not optimized for speed, memory or IO.
 *
 * Eventually this capability should become part of the Shark public APIs, please
 * open an issue if you'd like to use this directly.
 */
class ObjectDominators {

  internal data class DominatorNode(
    val shallowSize: Int,
    val retainedSize: Int,
    val retainedCount: Int,
    val dominatedObjectIds: List
  )

  fun renderDominatorTree(
    graph: HeapGraph,
    ignoredRefs: List,
    minRetainedSize: Int,
    threadName: String? = null,
    printStringContent: Boolean = false
  ): String {
    val stringBuilder = StringBuilder()

    val dominatorTree = buildDominatorTree(graph, ignoredRefs)

    val root = dominatorTree.getValue(ValueHolder.NULL_REFERENCE)
    stringBuilder.append(
      "Total retained: ${root.retainedSize} bytes in ${root.retainedCount} objects. Root dominators: ${root.dominatedObjectIds.size}\n\n"
    )

    val rootIds = if (threadName != null) {
      setOf(graph.gcRoots.first { gcRoot ->
        gcRoot is ThreadObject &&
          graph.objectExists(gcRoot.id) &&
          graph.findObjectById(gcRoot.id)
            .asInstance!!["java.lang.Thread", "name"]!!
            .value.readAsJavaString() == threadName
      }.id)
    } else {
      root.dominatedObjectIds.filter { dominatorTree.getValue(it).retainedSize > minRetainedSize }
    }

    rootIds
      .forEach { objectId ->
        printTree(
          stringBuilder, graph, dominatorTree, objectId, minRetainedSize, 0, "", true,
          printStringContent
        )
        stringBuilder.append("\n")
      }
    return stringBuilder.toString()
  }

  @Suppress("LongParameterList")
  private fun printTree(
    stringBuilder: StringBuilder,
    graph: HeapGraph,
    tree: Map,
    objectId: Long,
    minSize: Int,
    depth: Int,
    prefix: String,
    isLast: Boolean,
    printStringContent: Boolean
  ) {
    val node = tree.getValue(objectId)
    val heapObject = graph.findObjectById(objectId)
    val className = when (heapObject) {
      is HeapClass -> "class ${heapObject.name}"
      is HeapInstance -> heapObject.instanceClassName
      is HeapObjectArray -> heapObject.arrayClassName
      is HeapPrimitiveArray -> heapObject.arrayClassName
    }
    val anchor = if (depth == 0) "" else if (isLast) "╰─" else "├─"
    val size = if (node.retainedSize != node.shallowSize) {
      "${node.retainedSize} bytes (${node.shallowSize} self)"
    } else {
      "${node.shallowSize} bytes"
    }
    val count = if (node.retainedCount > 1) {
      " ${node.retainedCount} objects"
    } else {
      ""
    }
    val stringContent = if (
      printStringContent &&
      heapObject is HeapInstance &&
      heapObject.instanceClassName == "java.lang.String"
    ) " \"${heapObject.readAsJavaString()}\"" else ""
    stringBuilder.append(
      "$prefix$anchor$className #${heapObject.objectIndex} Retained: $size$count$stringContent\n"
    )

    val newPrefix = when {
      depth == 0 -> ""
      isLast -> {
        "$prefix  "
      }
      else -> {
        "$prefix│ "
      }
    }

    val largeChildren = node.dominatedObjectIds.filter { tree.getValue(it).retainedSize > minSize }
    val lastIndex = node.dominatedObjectIds.lastIndex

    largeChildren.forEachIndexed { index, objectId ->
      printTree(
        stringBuilder,
        graph, tree, objectId, minSize, depth + 1, newPrefix,
        index == lastIndex,
        printStringContent
      )
    }
    if (largeChildren.size < node.dominatedObjectIds.size) {
      stringBuilder.append("$newPrefix╰┄\n")
    }
  }

  private fun buildDominatorTree(
    graph: HeapGraph,
    ignoredRefs: List
  ): Map {
    val pathFinder = PathFinder(
      graph,
      OnAnalysisProgressListener.NO_OP, ignoredRefs
    )
    val nativeSizeMapper = AndroidNativeSizeMapper(graph)
    val nativeSizes = nativeSizeMapper.mapNativeSizes()
    val shallowSizeCalculator = ShallowSizeCalculator(graph)

    val result = pathFinder.findPathsFromGcRoots(setOf(), true)
    return result.dominatorTree!!.buildFullDominatorTree { objectId ->
      val nativeSize = nativeSizes[objectId] ?: 0
      val shallowSize = shallowSizeCalculator.computeShallowSize(objectId)
      nativeSize + shallowSize
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy