跳到内容
🤔 文档有问题? 报告编辑

Shark 🦈

SharkSmart Heap Analysis Reports for Kotlin

Shark 是为 LeakCanary 2 提供支持的堆分析器。它是一个 Kotlin 独立堆分析库,具有高速低内存占用的特点。

Shark 分层发布

  1. Shark Hprof:读取和写入 hprof 文件中的记录。
  2. Shark Graph:遍历堆对象图。
  3. Shark:生成堆分析报告。
  4. Shark Android:用于生成定制堆分析报告的 Android 启发式算法。
  5. Shark CLI:分析连接到你桌面的 Android 设备上已安装的可调试应用的堆。其输出类似于 LeakCanary 的输出,但你无需将 LeakCanary 依赖项添加到你的应用中。
  6. LeakCanary:在此基础上构建。它会自动监视已销毁的 Activity 和 Fragment,触发堆转储,运行 Shark Android,然后显示结果。

更多内容

  • Shark 构建在 Okio 之上。Okio 使高效解析堆转储成为可能。
  • Shark 是一个 100% Kotlin 库,Kotlin 对其设计至关重要,因为 Shark 大量依赖密封类和序列来节省内存。
  • Shark 具有独特的能力,可以通过特定于平台的启发式算法帮助缩小内存泄漏的原因范围。
  • Shark 经过严格测试(80% 测试覆盖率)。
  • Shark 可以在 Java 和 Android 虚拟机中运行,除了 Okio 和 Kotlin,没有其他依赖。
  • Shark 可以分析 Java 和 Android 虚拟机生成的 hprof 文件。
  • 如果可以访问混淆映射文件,Shark 可以对 hprof 记录进行去混淆。

Shark CLI

Shark 命令行界面(CLI)使你能够直接从电脑上分析堆。它可以转储连接的 Android 设备上已安装应用的堆,对其进行分析,甚至可以从堆转储中去除任何敏感数据(例如个人身份信息、密码或加密密钥),这在共享堆转储时非常有用。

通过 Homebrew 安装

brew install leakcanary-shark

你也可以在此下载。

然后,你可以在任何连接的设备上的应用中查找内存泄漏,例如

$ shark-cli --device emulator-5554 --process com.example.app.debug analyze

信息

shark-cli 适用于所有可调试应用,即使它们不包含 leakcanary-android 依赖项。

运行 shark-cli 查看使用说明

$ shark-cli

Usage: shark-cli [OPTIONS] COMMAND [ARGS]...

                   ^`.                 .=""=.
   ^_              \  \               / _  _ \
   \ \             {   \             |  d  b  |
   {  \           /     `~~~--__     \   /\   /
   {   \___----~~'              `~~-_/'-=\/=-'\,
    \                         /// a  `~.      \ \
    / /~~~~-, ,__.    ,      ///  __,,,,)      \ |
    \/      \/    `~~~;   ,---~~-_`/ \        / \/
                     /   /            '.    .'
                    '._.'             _|`~~`|_
                                      /|\  /|\

Options:
  -p, --process TEXT              Full or partial name of a process, e.g.
                                  "example" would match "com.example.app"
  -d, --device ID                 device/emulator id
  -m, --obfuscation-mapping PATH  path to obfuscation mapping file
  --verbose / --no-verbose        provide additional details as to what
                                  shark-cli is doing
  -h, --hprof FILE                path to a .hprof file
  --help                          Show this message and exit

Commands:
  interactive   Explore a heap dump.
  analyze       Analyze a heap dump.
  dump-process  Dump the heap and pull the hprof file.
  strip-hprof   Replace all primitive arrays from the provided heap dump with
                arrays of zeroes and generate a new "-stripped.hprof" file.

Shark 代码示例

读取 hprof 文件中的记录

// build.gradle
dependencies {
  implementation 'com.squareup.leakcanary:shark-hprof:$sharkVersion'
}
import java.io.File
import shark.Hprof
import shark.HprofRecord.StringRecord
import shark.OnHprofRecordListener

fun main(args: Array<String>) {
  val heapDumpFile = File(args[0])

  // Prints all class and field names
  Hprof.open(heapDumpFile).use { hprof ->
    hprof.reader.readHprofRecords(
      recordTypes = setOf(StringRecord::class),
      listener =
        OnHprofRecordListener { position, record -> println((record as StringRecord).string) },
    )
  }
}
// build.gradle
dependencies {
  implementation 'com.squareup.leakcanary:shark-graph:$sharkVersion'
}
import java.io.File
import shark.Hprof
import shark.HprofHeapGraph

fun main(args: Array<String>) {
  val heapDumpFile = File(args[0])

  // Prints all thread names
  Hprof.open(heapDumpFile).use { hprof ->
    val heapGraph = HprofHeapGraph.indexHprof(hprof)
    val threadClass = heapGraph.findClassByName("java.lang.Thread")!!
    val threadNames: Sequence<String> =
      threadClass.instances.map { instance ->
        val nameField = instance["java.lang.Thread", "name"]!!
        nameField.value.readAsJavaString()!!
      }
    threadNames.forEach { println(it) }
  }
}

生成堆分析报告

// build.gradle
dependencies {
  implementation 'com.squareup.leakcanary:shark:$sharkVersion'
}
import java.io.File
import shark.FilteringLeakingObjectFinder
import shark.FilteringLeakingObjectFinder.LeakingObjectFilter
import shark.HeapAnalyzer
import shark.HeapObject
import shark.HeapObject.HeapInstance
import shark.Hprof
import shark.HprofHeapGraph

// Marks any instance of com.example.ThingWithLifecycle with
// ThingWithLifecycle.destroyed=true as leaking
val leakingObjectFilter =
  object : LeakingObjectFilter {
    override fun isLeakingObject(heapObject: HeapObject): Boolean {
      return if (
        heapObject is HeapInstance && heapObject instanceOf "com.example.ThingWithLifecycle"
      ) {
        val destroyedField = heapObject["com.example.ThingWithLifecycle", "destroyed"]!!
        destroyedField.value.asBoolean!!
      } else false
    }
  }

val leakingObjectFinder = FilteringLeakingObjectFinder(listOf(leakingObjectFilter))

fun main(args: Array<String>) {
  val heapDumpFile = File(args[0])
  val heapAnalysis =
    Hprof.open(heapDumpFile).use { hprof ->
      val heapGraph = HprofHeapGraph.indexHprof(hprof)
      val heapAnalyzer = HeapAnalyzer({})
      heapAnalyzer.analyze(
        heapDumpFile = heapDumpFile,
        graph = heapGraph,
        leakingObjectFinder = leakingObjectFinder,
      )
    }
  println(heapAnalysis)
}

生成 Android 堆分析报告

// build.gradle
dependencies {
  implementation 'com.squareup.leakcanary:shark-android:$sharkVersion'
}
import java.io.File
import shark.AndroidObjectInspectors
import shark.AndroidReferenceMatchers
import shark.FilteringLeakingObjectFinder
import shark.FilteringLeakingObjectFinder.LeakingObjectFilter
import shark.HeapAnalyzer
import shark.HeapObject
import shark.HeapObject.HeapInstance
import shark.Hprof
import shark.HprofHeapGraph

// Marks any instance of com.example.ThingWithLifecycle with
// ThingWithLifecycle.destroyed=true as leaking
val leakingObjectFilter =
  object : LeakingObjectFilter {
    override fun isLeakingObject(heapObject: HeapObject): Boolean {
      return if (
        heapObject is HeapInstance && heapObject instanceOf "com.example.ThingWithLifecycle"
      ) {
        val instance = heapObject as HeapInstance
        val destroyedField = instance["com.example.ThingWithLifecycle", "destroyed"]!!
        destroyedField.value.asBoolean!!
      } else false
    }
  }

val leakingObjectFinder = FilteringLeakingObjectFinder(listOf(leakingObjectFilter))

fun main(args: Array<String>) {
  val heapDumpFile = File(args[0])

  val heapAnalysis =
    Hprof.open(heapDumpFile).use { hprof ->
      val heapGraph = HprofHeapGraph.indexHprof(hprof)
      val heapAnalyzer = HeapAnalyzer({})
      heapAnalyzer.analyze(
        heapDumpFile = heapDumpFile,
        graph = heapGraph,
        leakingObjectFinder = leakingObjectFinder,
        referenceMatchers = AndroidReferenceMatchers.appDefaults,
        objectInspectors = AndroidObjectInspectors.appDefaults,
      )
    }

  println(heapAnalysis)
}