为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!(上)

简介: 为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!

前言


LeakCanary 是我们非常熟悉内存泄漏检测工具,它能够帮助开发者非常高效便捷地检测 Android 中常见的内存泄漏。在各大厂自研的内存泄漏检测框架(如腾讯 Matrix 和快手 Koom)的帮助文档中,也会引述 LeakCanary 原理分析。


不吹不黑,LeakCanary 源码中除了实现内存泄漏的监控方案外,还有非常多值得学习的编程技巧,只有沉下心去阅读的人才能够真正体会到。在这篇文章里,我将带你从入门开始掌握 LeakCanary 的使用场景以及使用方法,再介绍 LeakCanary 的工作流程和高级用法,最后通过源码解析深入理解原理。本文示例程序已上传到 Github: DemoHall · HelloLeakCanary ,有用请给 Star 支持,谢谢。


学习路线图:

image.png

1. 认识 LeakCanary


1.1 什么是内存泄漏?


内存泄露(Memory Leaks)指不再使用的对象或数据没有被回收,随着内存泄漏的堆积,应用性能会逐渐变差,甚至发生 OOM  奔溃。在 Android 应用中的内存泄漏可以分为 2 类:


  • Java 内存泄露: 不再使用的对象被生命周期更长的 GC Root 引用,无法被判定为垃圾对象而导致内存泄漏(LeakCanary 只能监控 Java 内存泄漏);
  • Native 内存泄露: Native 内存没有垃圾回收机制,未手动回收导致内存泄漏。


1.2 为什么要使用 LeakCanary?


LeakCanray 是 Square 开源的 Java 内存泄漏分析工具,用于在实验室阶段检测 Android 应用中常见中的内存泄漏。


LeakCanary 的特点或优势在于提前预判出 Android 应用中最常见且影响较大的内存泄漏场景,并对此做针对性的监测手段。 这使得 LeakCanary 相比于其他排查内存泄漏的方案(如分析 OOM 异常时的堆栈日志、MAT 分析工具)更加高效。因为当内存泄漏堆积而内存不足时,应用可能从任何一次无关紧要的内存分配中抛出 OOM,堆栈日志只能体现最后一次内存分配的堆栈信息,而无法体现出导致发生 OOM 的主要原因。

目前,LeakCanary 支持以下五种 Android 场景中的内存泄漏监测:


  • 1、已销毁的 Activity 对象(进入 DESTROYED 状态);
  • 2、已销毁的 Fragment 对象和 Fragment View 对象(进入 DESTROYED 状态);
  • 3、已清除的的 ViewModel 对象(进入 CLEARED 状态);
  • 4、已销毁的的 Service 对象(进入 DESTROYED 状态);
  • 5、已从 WindowManager 中移除的 RootView 对象;


1.3 LeakCanary 怎么实现内存泄漏监控?


LeakCanary 通过以下 2 点实现内存泄漏监控:


  • 1、在 Android Framework 中注册无用对象监听: 通过全局监听器或者 Hook 的方式,在 Android Framework 上监听 Activity 和 Service 等对象进入无用状态的时机(例如在 Activity#onDestroy() 后,产生一个无用 Activity 对象);
  • 2、利用引用对象可感知对象垃圾回收的机制判定内存泄漏: 为无用对象包装弱引用,并在一段时间后(默认为五秒)观察弱引用是否如期进入关联的引用队列,是则说明未发生泄漏,否则说明发生泄漏(无用对象被强引用持有,导致无法回收,即泄漏)。


详细的源码分析下文内容。


2. 理解 LeakCanary 的工作流程

虽然 LeakCanary 的使用方法非常简单,但是并不意味着 LeakCanary 的工作流程也非常简单。在了解 LeakCanary 的使用方法和深入 LeakCanary 的源码之前,我们先理解 LeakCanary 的核心工作流程,我将其概括为以下 5 个阶段:

image.png


  • 1、注册无用对象监听: 在 Android Framework 中注册监听器,感知五种 Android 内存泄漏场景中产生无用对象的时机(例如在 Activity#onDestroy() 后,产生一个无用 Activity 对象);
  • 2、监控内存泄漏: 为无用对象关联弱引用对象,如果一段时间后引用对象没有按预期进入引用队列,则认为对象发生内存泄漏。由于分析堆快照是耗时工作,所以 LeakCanary 不会每次发现内存泄漏对象都进行分析工作,而是内存泄漏对象计数到达阈值才会触发分析工作。在计数未到达阈值的过程中,LeakCanary 会发送一条系统通知,你也可以点击该通知提前触发分析工作;


收集过程中的系统通知消息

image.png


提示: LeakCanary 为不同的 App 状态设置了不同默认阈值:App 可见时阈值为 5 个泄漏对象,App 不可见时阈值为 1 个泄漏对象。举个例子,如果 App 在前台可见并且已经收集了 4 个泄漏的对象,此时 App 退到后台,LeakCanary 会在五秒后触发分析工作。


  • 3、Java Heap Dump: 当泄漏对象计数达到阈值时,会触发 Java Heap Dump 并生成 .hprof 文件存储到文件系统中。Heap Dump 的过程中会锁堆,会使应用冻结一段时间;

Heap Dump 过程中的全局对话框

image.png


  • 4、分析堆快照: LeakCanary 会根据应用的依赖项,选择 WorkManager 多进程、WorkManager 异步任务或 Thread 异步任务其中一种策略来执行分析(例如,LeakCanary 会检查应用有 leakcanary-android-process 依赖项,才会使用 WorkManager 多进程策略)。分析过程 LeakCanary 使用 Shark 分析 .hprof 文件,替换了 LeakCanary 1.0 使用的 haha
  • 5、输出分析报告: 当分析工作完成后,LeakCanary 会在 Logcat 打印分析结果,也会发送一条系统通知消息。点击通知消息可以跳转到可视化分析报告页面,也可以点击 LeakCanary 生成的桌面快捷方式进入。

分析结束后的系统通知消息


image.png

新增的启动图标


image.png


可视化分析报告


image.png

至此,LeakCanary 一次内存泄漏分析工作流程执行完毕。


3. LeakCanary 的基本用法


这一节,我们来介绍 LeakCanary 的基础用法。


3.1 将 LeakCanary 添加到项目中


在 build.gradle 中添加 LeakCanary 依赖,此外不需要调用任何初始化 API(LeakCanary 内部默认使用了 ContentProvider 实现无侵入初始化)。另外,因为 LeakCanary 是只在实验室环境使用的工具,所以这里要记得使用 debugImplementation 依赖配置。


build.gradle


dependencies {
    // debugImplementation because LeakCanary should only run in debug builds.
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
}
复制代码


3.2 手动初始化 LeakCanary


LeakCanary 2.0 默认采用了 ContentProvider 机制实现了无侵入初始化,为了给予开发者手动初始化 LeakCanary 的可能性,LeakCanary 在 ContentProvider 中设置了布尔值开关:


AndroidManifest.xml


<application>
    <provider
        android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
        android:authorities="${applicationId}.leakcanary-installer"
        android:enabled="@bool/leak_canary_watcher_auto_install"
        android:exported="false"/>
</application>
复制代码


开发者只需要在资源文件里覆写 @bool/eak_canary_watcher_auto_install 布尔值来关闭自动初始化,并在合适的时机手动调用 AppWatcher#manualInstall

values.xml


<resources>
    <bool name="leak_canary_watcher_auto_install">false</bool>
</resources>
复制代码


3.3 自定义 LeakCanary 配置


LeakCanary 为开发者提供了便捷的配置 API,并且这个配置 API 在初始化前后都允许调用。


示例程序


// Java 语法
LeakCanary.Config config = LeakCanary.getConfig().newBuilder()
    .retainedVisibleThreshold(3)
    .build();
LeakCanary.setConfig(config);
复制代码
// Kotlin 语法
LeakCanary.config = LeakCanary.config.copy(
    retainedVisibleThreshold = 3
)
复制代码


以下用一个表格总结 LeakCanary 主要的配置项:


配置项 描述 默认值
dumpHeap: Boolean Heap Dump 分析开关 true
dumpHeapWhenDebugging: Boolean 调试时 Heap Dump 分析开关 false
retainedVisibleThreshold: Int App 可见时泄漏计数阈值 5
objectInspectors: List 对象检索器 AndroidObjectInspectors.appDefaults
computeRetainedHeapSize: Boolean 是否计算泄漏内存空间 true
maxStoredHeapDumps: Int 最大堆快照存储数量 7
requestWriteExternalStoragePermission: Boolean 是否请求文件存储权限 true
leakingObjectFinder: LeakingObjectFinder 引用链分析器 KeyedWeakReferenceFinder
heapDumper: HeapDumper Heap Dump 执行器 Debug.dumpHprofData
eventListeners: List 事件监听器 多个内部监听器



4. 解读 LeakCanary 分析报告


内存泄漏分析报告是 LeakCanary 所有监控和分析工作后输出的目标产物,要根据修复内存泄漏,首先就要求开发者能够读懂 LeakCanary 的分析报告。我将 LeakCanary 的分析报告总结为以下 4 个要点:


4.1 泄漏对象的引用链


泄漏对象的引用链是分析报告的核心信息,LeakCanary 会收集泄漏对象到 GC Root 的完整引用链信息。例如,以下示例程序在 static 变量中持有一个 Helper 对象,当 Helper 被期望被垃圾回收时用 AppWatcher 监测该对象,如果未按预期被回收,则会输出以下分析报告:


示例程序


class Helper {
}
class Utils {
    public static Helper helper = new Helper();
}
// Helper 无用后监测
AppWatcher.objectWatcher.watch(helper, "Helper is no longer useful")
复制代码


Logcat 日志


┬───
│ GC Root: Local variable in native code
├─ dalvik.system.PathClassLoader instance
│    ↓ PathClassLoader.runtimeInternalObjects // 表示 PathClassLoader 中的 runtimeInternalObjects 字段,它是一个 Object 数组
├─ java.lang.Object[] array
│    ↓ Object[].[43] // 表示 Object 数组的第 43 位,它是一个 Utils 类型引用
├─ com.example.Utils class
│    ↓ static Utils.helper // 表示 Utils 的 static 字段,它是一个 Helper 类型引用
╰→ java.example.Helper
复制代码


解释一下其中的符号:


  • 代表一个 Java 对象;
  • │ ↓ 代表一个 Java 引用,关联的实际对象在下一行;
  • ╰→ 代表泄漏的对象,即 AppWatcher.objectWatcher.watch() 直接监控的对象。


4.2 按引用链签名分组


用减少重复的排查工作,LeakCanary 会将相同问题重复触发的内存泄漏进行分组,分组方法是按引用链的签名。引用链签名是对引用链上经过的每个对象的类型拼接后取哈希值,既然应用链完全相同,就没必要重复排查了。


image.png

例如,对于泄漏对象 instance,对应的泄漏签名计算公式如下:

Logcat 日志


...
├─ com.example.leakcanary.LeakingSingleton class
│    Leaking: NO (a class is never leaking)
│    ↓ static LeakingSingleton.leakedViews
│                              ~~~~~~~~~~~
├─ java.util.ArrayList instance
│    Leaking: UNKNOWN
│    ↓ ArrayList.elementData
│                ~~~~~~~~~~~
├─ java.lang.Object[] array
│    Leaking: UNKNOWN
│    ↓ Object[].[0]
│               ~~~
├─ android.widget.TextView instance
│    Leaking: YES (View.mContext references a destroyed activity)
复制代码

对应的签名计算公式


val leakSignature = sha1Hash(
    "com.example.leakcanary.LeakingSingleton.leakedView" +
    "java.util.ArrayList.elementData" +
    "java.lang.Object[].[x]"
)
println(leakSignature)
// dbfa277d7e5624792e8b60bc950cd164190a11aa
复制代码


4.3 使用 ~~~ 标记怀疑对象


为了提高排查内存泄漏的效率,LeakCanary 会自动帮助我们根据对象的生命周期信息或状态信息缩小排查范围,排除原本就具有全局生命周期的对象,剩下的用 ~~~ 下划线标记为怀疑对象。


例如,在以下内存泄漏报告中,ExampleApplication 对象被 FontsContract.sContext 静态变量持有,表面看起来是 sContext 静态变量导致内存泄漏。其实不是,因为 ExampleApplication 的生命周期是全局的且永远不会被垃圾回收的,所以内存泄漏的根本原因一定不是因为 sContext 持有 ExampleApplication 引起的,sContext 这条引用可以排除,所以它不会用 ~~~ 下划线标记。


image.png

4.4 按 Application Leaks 和 Library Leaks 分类


为了提高排查内存泄漏的效率,LeakCanary 会自动将泄漏报告划分为 2 类:

  • Application Leaks: 应用层代码产生的内存泄漏,包括项目代码和第三方库代码;
  • Library Leaks: Android Framework 产生的内存泄漏,开发者几乎无法做什么,可以忽略。


其实,Library Leaks 这个名词起得并不好,应该叫作 Framework Leaks。 小彭最初在阅读官方文档后,以为 Library Leaks 是只第三方库代码产生的内存泄漏,LeakCanary 还提到开发者对于 Library Leaks 几乎无法做什么,让我一度很好奇 LeakCanary 是如何定义二方库和三方库。最后还是通过源码才得知,Library Leaks 原来是指 Android Framework 中产生的内存泄漏,例如什么 TextView、InputMethodManager 之类的。

Logcat 中的 Library Leak 标记


====================================
HEAP ANALYSIS RESULT
====================================
0 APPLICATION LEAKS
====================================
1 LIBRARY LEAK
...
┬───
│ GC Root: Local variable in native code
...
复制代码


可视化分析报告中的 Library Leak 标记


image.png


5. LeakCanary 的进阶用法


5.1 使用 App Startup 初始化 LeakCanary


LeakCanary 2.8 提供了对 Jetpack · App Startup 的支持。如果想使用 App Startup 初始化 LeakCanary,只需要替换为另一个依赖。不过,毕竟 LeakCanary 是主要在实验室环境使用的工具,这个优化的意义并不大。


build.gradle


dependencies {
    // 替换为另一个依赖
    // debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
    debugImplementation 'com.squareup.leakcanary:leakcanary-android-startup:2.9.1'
}
复制代码


对应的 App Startup 启动器源码:

AppWatcherStartupInitializer.kt


internal class AppWatcherStartupInitializer : Initializer<AppWatcherStartupInitializer> {
    override fun create(context: Context) = apply {
        val application = context.applicationContext as Application
        AppWatcher.manualInstall(application)
    }
    override fun dependencies() = emptyList<Class<out Initializer<*>>>()
}
复制代码

5.2 在子进程执行 LeakCanary 分析工作


由于 LeakCanary 分析堆快照的过程存在一定的内存消耗,整个分析过程一般会持续几十秒,对于一些性能差的机型会造成明显的卡顿甚至 ANR。为了优化内存占用和卡顿问题,LeakCanary 2.8 提供了对多进程的支持。开发者只需要依赖 LeakCanary 的多进程依赖项,LeakCanary 会自动将分析工作转移到子进程中(基于 androidX.work.multiprocess):


build.gradle


dependencies {
    // 官方文档对多进程功能的介绍有矛盾,经过测试,以下两个依赖都需要
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
    debugImplementation 'com.squareup.leakcanary:leakcanary-android-process:2.9.1'
}
复制代码

同时,开发者需要在自定义 Application 中检查当前进程信息,避免在 LeakCanary 的子进程中执行不必要的初始化操作:


ExampleApplication.kt


class ExampleApplication : Application() {
    override fun onCreate() {
        if (LeakCanaryProcess.isInAnalyzerProcess(this)) {
            return
        }
        super.onCreate()
        // normal init goes here, skipped in :leakcanary process.
    }
}
复制代码

Logcat 进程选项


image.png

Logcat 日志

LeakCanary: Enqueuing heap analysis for /storage/emulated/0/Download/leakcanary-com.pengxr.helloleakcanary/2022-08-22_19-54-24_331.hprof on WorkManager remote worker
复制代码

5.3 使用快手 Koom 加快 Dump 速度


LeakCanary 默认的 Java Heap Dump 使用的是 Debug.dumpHprofData() ,在 Dump 的过程中会有较长时间的应用冻结时间。 快手技术团队在开源框架 Koom 中提出了优化方案:利用 Copy-on-Write 思想,fork 子进程再进行 Heap Dump 操作。

LeakCanary 配置项可以修改 Heap Dump 执行器,示例程序如下:

示例程序


// 依赖: 
debugImplementation "com.kuaishou.koom:koom-java-leak:2.2.0"
// 使用默认配置初始化 Koom
DefaultInitTask.init(application)
// 自定义 LeakCanary 配置
LeakCanary.config = LeakCanary.config.copy(
    // 自定义 Heap Dump 执行器
    heapDumper = {
        ForkJvmHeapDumper.getInstance().dump(it.absolutePath)
    }
)
复制代码

Logcat 日志对比


// 使用默认的 Debug.dumpHprofData() 的日志
helloleakcanar: hprof: heap dump "/storage/emulated/0/Download/leakcanary-com.pengxr.helloleakcanary/2022-08-22_18-47-28_674.hprof" starting...
helloleakcanar: hprof: heap dump completed (34MB) in 1.552s objects 549530 objects with stack traces 0
LeakCanary: Enqueuing heap analysis for /storage/emulated/0/Download/leakcanary-com.pengxr.helloleakcanary/2022-08-22_19-58-13_310.hprof on WorkManager remote worker
...
// 使用快手 Koom Heap Dump 的日志
OOMMonitor_ForkJvmHeapDumper: dump /storage/emulated/0/Download/leakcanary-com.pengxr.helloleakcanary/2022-08-22_19-54-24_331.hprof
OOMMonitor_ForkJvmHeapDumper: before suspend and fork.
OOMMonitor_ForkJvmHeapDumper: dump true, notify from pid 8567
LeakCanary: Enqueuing heap analysis for /storage/emulated/0/Download/leakcanary-com.pengxr.helloleakcanary/2022-08-22_19-54-24_331.hprof on WorkManager remote worker
...
复制代码


看一眼 Koom 源码:

ForkJvmHeapDumper.java


public synchronized boolean dump(String path) {
    boolean dumpRes = false;
    int pid = suspendAndFork();
    if (pid == 0) {
        // Child process
        Debug.dumpHprofData(path);
        exitProcess();
    } else if (pid > 0) {
        // Parent process
        dumpRes = resumeAndWait(pid);
    }
    return dumpRes;
}
private native void nativeInit();
private native int suspendAndFork();
private native boolean resumeAndWait(int pid);
private native void exitProcess();
复制代码


5.4 自定义标记引用信息


LeakCanary 配置项可以自定义 ObjectInspector 对象检索器,在引用链上的节点中标记必要的信息和状态。标记信息会显示在分析报告中,并且会影响报告中的提示。

  • notLeakingReasons 标记: 标记非泄漏原因后,节点为 NOT_LEAKING 状态,并在分析报告中会显示 Leaking: NO (notLeakingReasons)
  • leakingReasons 标记: 标记泄漏原因后,节点为 LEAKING 状态,在分析报告中会显示 Leaking: YES (leakingReasons)
  • 缺省: 节点为 UNKNOWN 状态,在分析报告中会显示 Leaking: UNKNOWN

示例程序如下:


示例程序


// 自定义 LeakCanary 配置
LeakCanary.config = LeakCanary.config.copy(
    // 自定义对象检索器
    objectInspectors = LeakCanary.config.objectInspectors + ObjectInspector { reporter ->
        // reporter.notLeakingReasons += "非泄漏原因"
        // reporter.leakingReasons += "泄漏原因"
    } + AppSingletonInspector(
        // 标记全局类的类名即可
    )
)
复制代码


另外,引用链 LEAKING 节点以后到第一个 NOT_LEAKING 节点中间的节点,才会用 ~~~ 下划线标记为怀疑对象。例如:


image.png


6. LeakCanary 实现原理分析


使用一张示意图表示 LeakCanary 的基本架构:


image.png


6.1 LeakCanary 如何实现自动初始化?


旧版本的 LeakCanary 需要在 Application 中调用相关初始化 API,而在 LeakCanary v2 版本中却不再需要手动初始化,为什么呢?—— 这是因为 LeakCanary 利用了 ContentProvider 的初始化机制来间接调用初始化 API。

ContentProvider 的常规用法是提供内容服务,而另一个特殊的用法是提供无侵入的初始化机制,这在第三方库中很常见,Jetpack 中提供的轻量级初始化框架 App Startup 也是基于 ContentProvider 的方案。

image.png


MainProcessAppWatcherInstaller.kt


internal class MainProcessAppWatcherInstaller : ContentProvider() {
    override fun onCreate(): Boolean {
        // 初始化 LeakCanary
        val application = context!!.applicationContext as Application
        AppWatcher.manualInstall(application)
        return true
    }
    ...
}
复制代码


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
12天前
|
开发工具 Swift iOS开发
【Swift开发专栏】Swift中的内存泄漏检测与修复
【4月更文挑战第30天】本文探讨了Swift中的内存泄漏问题,尽管有ARC机制,但仍需关注内存管理。文章分为三部分:内存管理基础知识、检测方法和修复技巧。了解ARC原理和循环引用陷阱是防止内存泄漏的关键。检测方法包括使用Xcode内存调试器、LeakSanitizer和性能分析工具。修复技巧涉及打破循环引用、使用弱/无主引用及手动管理内存。理解这些对优化应用性能和稳定性至关重要。
|
2月前
|
存储 分布式计算 网络协议
阿里云服务器内存型r7、r8a、r8y实例区别参考
在阿里云目前的活动中,属于内存型实例规格的云服务器有内存型r7、内存型r8a、内存型r8y这几个实例规格,相比于活动内的经济型e、通用算力型u1实例来说,这些实例规格等性能更强,与计算型和通用型相比,它的内存更大,因此这些内存型实例规格主要适用于数据库、中间件和数据分析与挖掘,Hadoop、Spark集群等场景,本文为大家介绍内存型r7、r8a、r8y实例区别及最新活动价格,以供参考。
阿里云服务器内存型r7、r8a、r8y实例区别参考
|
3月前
|
缓存 监控 Python
在Python中,如何检测和处理内存泄漏?
【2月更文挑战第7天】【2月更文挑战第18篇】在Python中,如何检测和处理内存泄漏?
|
2月前
|
IDE Linux 开发工具
内存泄漏检测工具Valgrind:C++代码问题检测的利器(一)
内存泄漏检测工具Valgrind:C++代码问题检测的利器
139 0
|
12天前
|
Dart 前端开发 Java
【Flutter前端技术开发专栏】Flutter中的内存泄漏检测与解决
【4月更文挑战第30天】本文探讨了Flutter应用中的内存泄漏检测与解决方法。内存泄漏影响性能和用户体验,常见原因包括全局变量、不恰当的闭包使用等。开发者可借助`observatory`工具或`dart_inspector`插件监测内存使用。解决内存泄漏的策略包括避免长期持有的全局变量、正确管理闭包、及时清理资源、妥善处理Stream和RxDart订阅、正确 disposal 动画和控制器,以及管理原生插件资源。通过这些方法,开发者能有效防止内存泄漏,优化应用性能。
【Flutter前端技术开发专栏】Flutter中的内存泄漏检测与解决
|
12天前
|
数据可视化 Java 测试技术
【Go语言专栏】Go语言中的内存泄漏检测与修复
【4月更文挑战第30天】Go语言内存泄漏详解:概念、原因、检测与修复。内存泄漏由忘记释放内存、循环引用等引起,Go通过垃圾回收机制管理内存,但仍有泄漏风险。检测方法包括pprof、可视化工具、代码审查和单元测试。修复策略涉及优化代码、使用defer、减少全局变量、弱引用及及时释放资源。实践案例分析有助于理解和解决问题。了解内存管理,防止泄漏,提升Go应用性能和稳定性。
|
2月前
|
存储 缓存 PHP
阿里云服务器实例、CPU内存、带宽、操作系统选择参考
对于使用阿里云服务器的用户来说,云服务器的选择和使用非常重要,如果实例、内存、CPU、带宽等配置选择错误,可能会影响到自己业务在云服务器上的计算性能及后期运营状况,本文为大家介绍一下阿里云服务器实例、CPU内存、带宽、操作系统的选择注意事项,以供参考。
阿里云服务器实例、CPU内存、带宽、操作系统选择参考
|
2月前
|
缓存 Linux iOS开发
【C/C++ 集成内存调试、内存泄漏检测和性能分析的工具 Valgrind 】Linux 下 Valgrind 工具的全面使用指南
【C/C++ 集成内存调试、内存泄漏检测和性能分析的工具 Valgrind 】Linux 下 Valgrind 工具的全面使用指南
71 1
|
2月前
|
缓存 测试技术 开发工具
内存泄漏检测工具Valgrind:C++代码问题检测的利器(二)
内存泄漏检测工具Valgrind:C++代码问题检测的利器
38 0
|
2月前
|
Python
在Python中,如何检测和修复内存泄漏?
在Python中,如何检测和修复内存泄漏?
116 0