Matrix 源码阅读笔记 —— ResourcePlugin

废话环节

Matrix 相信很多国内的开发者都很熟悉,不熟悉的人去 Github 上搜一下。在大致读完 它的 ResourcePlugin 源码后,有一点感触。读国内大厂的开源项目的代码,有一个明显的感触,相对于国外知名大厂的开源项目的源码,比如 GoogleSquare,确实代码质量要低很多的,持续维护性也要低很多。而且 Github 上有 IssuePull Request 都有很多没有人处理的。腾讯还是国内开源做得非常不错的尚且如此,阿里可以说是更加完蛋。所以我认为国内大厂的开源库慎用吧,除非你能够自己 fork 一份来自己维护其中的代码。我阅读 ResourcePlugin 的源码时就发现他们一个核心功能的 Bug,本来想着提交一个 IssuePR 的,但是看到 Matrix 上成吨的没有处理的 IssuePR,我也不浪费自己的时间了。虽然直接使用他们的代码要慎重,但是还是有学习的价值,我也会在文章中指出我发现的 Bug

Matrix 的功能分为各种 Plugin 实现,所以我们按照不同的功能的 Plugin 去阅读源码就好了,今天是阅读 ResourcePlugin 的源码,它的主要功能是检测 Activity 的泄漏,相对于泄漏检测,LeakCanary 确实强大许多。

源码阅读

说了好多废话,直接以 ResourcePlugin#start() 方法作为源码阅读的入口函数:

Java 复制代码
@Override
public void start() {
    super.start();
    if (!isSupported()) {
        MatrixLog.e(TAG, "ResourcePlugin start, ResourcePlugin is not supported, just return");
        return;
    }
    mWatcher.start();
    MatrixHandlerThread.getDefaultHandler().post(new Runnable() {
        @Override
        public void run() {
            HprofFileManager.INSTANCE.checkExpiredFile();
        }
    });
}

直接调用了 ActivityRefWatcher#start() 方法:

Java 复制代码
@Override
public void start() {
    stopDetect();
    final Application app = mResourcePlugin.getApplication();
    if (app != null) {
        // 监听记录销毁的 Activity
        app.registerActivityLifecycleCallbacks(mRemovedActivityMonitor);
        scheduleDetectProcedure();
        MatrixLog.i(TAG, "watcher is started.");
    }
}

然后监听 Activity 的销毁,回调对象是 mRemovedActivityMonitor,还会通过 scheduleDetectProcedure() 方法开启一个后台任务,等下看这个后台任务。

先看看如何处理销毁的 Activity

Java 复制代码
private final Application.ActivityLifecycleCallbacks mRemovedActivityMonitor = new EmptyActivityLifecycleCallbacks() {

    @Override
    public void onActivityDestroyed(Activity activity) {
        pushDestroyedActivityInfo(activity);
        // 延迟 2s 触发 gc
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                triggerGc();
            }
        }, 2000);
    }
};

public void triggerGc() {
    long current = System.currentTimeMillis();
    if (mDumpHprofMode == ResourceConfig.DumpMode.NO_DUMP
            && current - lastTriggeredTime < getResourcePlugin().getConfig().getScanIntervalMillis() / 2 - 100) {
        MatrixLog.v(TAG, "skip triggering gc for frequency");
        return;
    }
    lastTriggeredTime = current;
    MatrixLog.v(TAG, "triggering gc...");
    Runtime.getRuntime().gc();
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        MatrixLog.printErrStackTrace(TAG, e, "");
    }
    Runtime.getRuntime().runFinalization();
    MatrixLog.v(TAG, "gc was triggered.");
}

首先通过 pushDestroyedActivityInfo() 方法将已经销毁的 Activity 添加到一个队列中,当然是以弱引用的方式持有 Activity 的实例,然后再延迟 2 秒手动触发一次 GC,这个触发 GC 的代码和 LeakCanary 中的一样,都是来自 AOSP 中的测试代码。

继续看看上面说到的后台任务:

Java 复制代码
private void scheduleDetectProcedure() {
    mDetectExecutor.executeInBackground(mScanDestroyedActivitiesTask);
}

    private final RetryableTask mScanDestroyedActivitiesTask = new RetryableTask() {

        @Override
        public Status execute() {
            // If destroyed activity list is empty, just wait to save power.
            // 如果当前没有被销毁的 Activity 等待
            if (mDestroyedActivityInfos.isEmpty()) {
                MatrixLog.i(TAG, "DestroyedActivityInfo is empty! wait...");
                synchronized (mDestroyedActivityInfos) {
                    try {
                        while (mDestroyedActivityInfos.isEmpty()) {
                            mDestroyedActivityInfos.wait();
                        }
                    } catch (Throwable ignored) {
                        // Ignored.
                    }
                }
                MatrixLog.i(TAG, "DestroyedActivityInfo is NOT empty! resume check");
                // 有新的被销毁的 Activity 了,重试
                return Status.RETRY;
            }

            // Fake leaks will be generated when debugger is attached.
            if (Debug.isDebuggerConnected() && !mResourcePlugin.getConfig().getDetectDebugger()) {
                MatrixLog.w(TAG, "debugger is connected, to avoid fake result, detection was delayed.");
                return Status.RETRY;
            }

//            final WeakReference<Object[]> sentinelRef = new WeakReference<>(new Object[1024 * 1024]); // alloc big object
            triggerGc();
//            if (sentinelRef.get() != null) {
//                // System ignored our gc request, we will retry later.
//                MatrixLog.d(TAG, "system ignore our gc request, wait for next detection.");
//                return Status.RETRY;
//            }

            final Iterator<DestroyedActivityInfo> infoIt = mDestroyedActivityInfos.iterator();

            // 默认 HprofMode 是 MANUAL_DUMP,DetectDebugger 为 false
            while (infoIt.hasNext()) {
                final DestroyedActivityInfo destroyedActivityInfo = infoIt.next();
                if ((mDumpHprofMode == ResourceConfig.DumpMode.NO_DUMP || mDumpHprofMode == ResourceConfig.DumpMode.AUTO_DUMP)
                        && !mResourcePlugin.getConfig().getDetectDebugger()
                        && isPublished(destroyedActivityInfo.mActivityName)) {
                    MatrixLog.v(TAG, "activity with key [%s] was already published.", destroyedActivityInfo.mActivityName);
                    infoIt.remove();
                    continue;
                }
                triggerGc();
                if (destroyedActivityInfo.mActivityRef.get() == null) {
                    // The activity was recycled by a gc triggered outside.
                    MatrixLog.v(TAG, "activity with key [%s] was already recycled.", destroyedActivityInfo.mKey);
                    infoIt.remove();
                    continue;
                }

                ++destroyedActivityInfo.mDetectedCount;

                if (destroyedActivityInfo.mDetectedCount < mMaxRedetectTimes
                        && !mResourcePlugin.getConfig().getDetectDebugger()) {
                    // Although the sentinel tell us the activity should have been recycled,
                    // system may still ignore it, so try again until we reach max retry times.
                    MatrixLog.i(TAG, "activity with key [%s] should be recycled but actually still exists in %s times, wait for next detection to confirm.",
                            destroyedActivityInfo.mKey, destroyedActivityInfo.mDetectedCount);

                    triggerGc();
                    continue;
                }

                MatrixLog.i(TAG, "activity with key [%s] was suspected to be a leaked instance. mode[%s]", destroyedActivityInfo.mKey, mDumpHprofMode);

                if (mLeakProcessor == null) {
                    throw new NullPointerException("LeakProcessor not found!!!");
                }
                
                // 处理泄漏
                if (mLeakProcessor.process(destroyedActivityInfo)) {
                    MatrixLog.i(TAG, "the leaked activity [%s] with key [%s] has been processed. stop polling", destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey);
                    infoIt.remove();
                }
            }

            return Status.RETRY;
        }
    };

后台任务的逻辑就是,一直在等待被销毁的 Activity 中的队列,如果有需要检测的 Activity 就再触发一次 GCGC 后再检查对应的 Activity 的弱引用是否还在,如果弱引用已经指向空了,那么表示通过了检查,把它从队列中移除,如果没有指向空,那就表示出现了泄漏,就继续下一步的处理。调用了 mLeakProcessor#process() 方法去处理,默认的实现方法是 ManualDumpProcessor#process() 方法。

Java 复制代码
@Override
public boolean process(final DestroyedActivityInfo destroyedActivityInfo) {
    getWatcher().triggerGc();

    if (destroyedActivityInfo.mActivityRef.get() == null) {
        MatrixLog.v(TAG, "activity with key [%s] was already recycled.", destroyedActivityInfo.mKey);
        return true;
    }

    MatrixLog.i(TAG, "show notification for activity leak. %s", destroyedActivityInfo.mActivityName);

    if (isMuted) {
        MatrixLog.i(TAG, "is muted, won't show notification util process reboot");
        return true;
    }

    Pair<String, String> data = dumpAndAnalyse(destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey);
    if (data != null) {
        if (!isMuted) {
            MatrixLog.i(TAG, "shown notification!!!3");
            sendResultNotification(destroyedActivityInfo, data.first, data.second);
        } else {
            MatrixLog.i(TAG, "mute mode, notification will not be shown.");
        }
    }


    return true;
}

继续调用 dumpAndAnalyse() 方法:

Java 复制代码
private Pair<String, String> dumpAndAnalyse(String activity, String key) {

    getWatcher().triggerGc();

    // 生成空的 HPROF 文件
    File file = null;
    try {
        file = HprofFileManager.INSTANCE.prepareHprofFile("MDP", false);
    } catch (FileNotFoundException e) {
        MatrixLog.printErrStackTrace(TAG, e, "");
    }

    if (file == null) {
        MatrixLog.e(TAG, "prepare hprof file failed, see log above");
        return null;
    }

    // 执行 Dump HPROF 文件和执行分析
    final ActivityLeakResult result = MemoryUtil.dumpAndAnalyze(file.getAbsolutePath(), key, 600);
    if (result.mLeakFound) {
        final String leakChain = result.toString();
        publishIssue(
                SharePluginInfo.IssueType.LEAK_FOUND,
                ResourceConfig.DumpMode.MANUAL_DUMP,
                activity, key, leakChain, String.valueOf(result.mAnalysisDurationMs),
                0,
                file.getAbsolutePath()
        );
        return new Pair<>(file.getAbsolutePath(), leakChain);
    } else if (result.mFailure != null) {
        publishIssue(
                SharePluginInfo.IssueType.ERR_EXCEPTION,
                ResourceConfig.DumpMode.MANUAL_DUMP,
                activity, key, result.mFailure.toString(), "0"
        );
        return null;
    } else {
        return new Pair<>(file.getAbsolutePath(), null);
    }
}

首先生成一个空的 HPROF 文件,然后调用 MemoryUtil.dumpAndAnalyze() 方法执行 dump 和 分析 HPROF 文件。

Kotlin 复制代码
@JvmStatic
@JvmOverloads
fun dumpAndAnalyze(
    hprofPath: String,
    referenceKey: String,
    timeout: Long = DEFAULT_TASK_TIMEOUT,
): ActivityLeakResult =
    createTask(hprofPath, referenceKey, timeout, ::forkDumpAndAnalyze)
Kotlin 复制代码
private fun createTask(
    hprofPath: String,
    referenceKey: String,
    timeout: Long,
    forkTask: (String, String, String, Long) -> Int
): ActivityLeakResult = initSafe { exception ->
    if (exception != null) return@initSafe ActivityLeakResult.failure(exception, 0)
    val analyzeStart = currentTime
    // 创建泄漏结果的写入文件
    val resultFile = createResultFile()
        ?: return ActivityLeakResult.failure(
            RuntimeException("Failed to create temporary analyze result file."), 0
        )
    val resultPath = resultFile.absolutePath
    // Native 创建一个子进程去解析 HPROF 文件和找到泄漏的路径,泄漏的路径最终会写入到 resultFile 中
    val leakResult = when (val pid = forkTask(hprofPath, resultPath, referenceKey, timeout)) {
        -1 -> ActivityLeakResult.failure(ForkException(), 0)
        else -> run { // current process
            info("Wait for task process [${pid}] complete executing.")
            // 等待子进程结束
            val result = waitTask(pid)
            if (result.exception != null) {
                info("Task process [${pid}] complete with error: ${result.exception!!.message}.")
                return@run ActivityLeakResult.failure(
                    result.exception, currentTime - analyzeStart
                )
            } else {
                info("Task process [${pid}] complete without error.")
            }
            return@run try {
                // 解析子进程最后的解析结果。
                val chains = deserialize(resultFile)
                if (chains.isEmpty()) {
                    // 没有泄漏
                    ActivityLeakResult.noLeak(currentTime - analyzeStart)
                } else {
                    // TODO: support reporting multiple leak chain.
                    chains.first().run {
                        ActivityLeakResult.leakDetected(
                            false,
                            nodes.last().objectName,
                            convertToReferenceChain(),
                            currentTime - analyzeStart
                        )
                    }
                }
            } catch (throwable: Throwable) {
                ActivityLeakResult.failure(throwable, currentTime - analyzeStart)
            }
        }
    }
    if (resultFile.exists()) resultFile.delete()
    return@initSafe leakResult
}

这个就是关键的方法了,首先创建一个泄漏结果的写入文件,Native 的代码会将结果写入到这个文件中;然后调用 forkTask() 方法来 fork 一个子进程来 dump 和分析 HPROF 文件。然后主进程等待子进程结束,解析的结果是写在文件中,然后通过 deserialize() 方法去反序列化解析结果。

forkTask() 方法实际上是指向 forkDumpAndAnalyze() ,它是一个 Native 方法,我们后面再看这个方法,在处理任务前首先会调用 initSafe() 方法初始化,我们先看看他的实现:

Kotlin 复制代码
/**
 * JNI 获取 TaskResult 的 JavaClass和对应的构造函数
 */
private external fun loadJniCache()

/**
 * 在 storageDirPath 目录下创建 ts 和 err 两个子文件夹
 */
private external fun syncTaskDir(storageDirPath: String)

/**
 * 获取 libart.so 中触发虚拟机 dump 相关的符号地址
 */
private external fun initializeSymbol()

private val initialized: InitializeException? = run {
    try {
        System.loadLibrary("matrix_mem_util")
        loadJniCache()
        syncTaskDir(storageDir.absolutePath)
        initializeSymbol()
        return@run null
    } catch (throwable: Throwable) {
        return@run InitializeException(throwable)
    }
}

执行初始化时,首先加载 so 库,然后分别调用 loadJniCache()syncTaskDir()initializeSymbol() 方法。他们都是 Native 方法。我们一个一个看。

C++ 复制代码
extern "C"
JNIEXPORT void JNICALL
Java_com_tencent_matrix_resource_MemoryUtil_loadJniCache(JNIEnv *env, jobject) {
    _info_log(TAG, "initialize: load JNI pointer cache");
    if (task_result_constructor == nullptr) {
        if (task_result_class == nullptr) {
            jclass local = env->FindClass("com/tencent/matrix/resource/MemoryUtil$TaskResult");
            if (local == nullptr) {
                log_and_throw_runtime_exception(env, "Failed to find class TaskResult");
                return;
            }
            // Make sure the class will not be unloaded.
            // See: https://developer.android.com/training/articles/perf-jni#jclass-jmethodid-and-jfieldid
            task_result_class = reinterpret_cast<jclass>(env->NewGlobalRef(local));
            if (task_result_class == nullptr) {
                log_and_throw_runtime_exception(env, "Failed to create global reference of class TaskResult");
                return;
            }
        }

        task_result_constructor =
                env->GetMethodID(task_result_class, "<init>", "(IIBLjava/lang/String;)V");
        if (task_result_constructor == nullptr) {
            log_and_throw_runtime_exception(env, "Failed to find constructor of class TaskResult");
            return;
        }
    }
}

上面的方法非常简单就是提前加载 TaskResult 类和它的构造函数,供后面使用。

C++ 复制代码
extern "C"
JNIEXPORT void JNICALL
Java_com_tencent_matrix_resource_MemoryUtil_syncTaskDir(JNIEnv *env, jobject, jstring path) {
    _info_log(TAG, "initialize: sync and create task info directories path");
    const char *value = env->GetStringUTFChars(path, nullptr);
    task_state_dir = ({
        std::stringstream builder;
        builder << value << "/ts";
        builder.str();
    });
    task_error_dir = ({
        std::stringstream builder;
        builder << value << "/err";
        builder.str();
    });
    env->ReleaseStringUTFChars(path, value);
    create_directory(env, task_state_dir.c_str());
    create_directory(env, task_error_dir.c_str());
}

上面代码就是初始化后面需要用到的结果输出目录。

initializeSymbol() 方法就要复杂一些了,他主要是去拿到虚拟机的 dump 内存相关的方法地址,在 Debug 类中也有 dump 内存的方法,但是只能 debug 的包可以调用,如果想要 release 包也能调用,就需要通过 Matrix 的处理方式,我们来看看它怎么做的。

C++ 复制代码
extern "C"
JNIEXPORT void JNICALL
Java_com_tencent_matrix_resource_MemoryUtil_initializeSymbol(JNIEnv *env, jobject) {
    _info_log(TAG, "initialize: initialize symbol");
    if (!initialize_symbols()) {
        log_and_throw_runtime_exception(env, "Failed to initialize symbol");
        return;
    }
}
C++ 复制代码
bool initialize_symbols() {
    android_version_ = android_get_device_api_level();
    if (android_version_ <= 0) return false;
    ds_mode(android_version_);

    // 手动加载虚拟机 libart.so 动态链接库
    auto *art_lib = ds_open("libart.so");

    if (art_lib == nullptr) {
        _error_log(TAG, "open libart.so failed");
        return false;
    }

#define load_symbol(ptr, type, sym, err)                    \
    ptr = reinterpret_cast<type>(ds_find(art_lib, sym));    \
    if ((ptr) == nullptr) {                                 \
        _error_log(TAG, err);                           \
        goto on_error;                                      \
    }

    // 加载 art::hprof::DumpHeap() 符号地址,写入到 dump_heap_ 指针中。
    load_symbol(dump_heap_,
                void(*)(const char *, int, bool ),
                "_ZN3art5hprof8DumpHeapEPKcib",
                "cannot find symbol art::hprof::DumpHeap()")

    if (android_version_ > __ANDROID_API_Q__) {
        // 加载 ScopedGCCriticalSection 构造函数和析构函数

        load_symbol(mirror::sgc_constructor,
                    void(*)(void * , mirror::Thread *, art::gc::GcCause, art::gc::CollectorType),
                    "_ZN3art2gc23ScopedGCCriticalSectionC1EPNS_6ThreadENS0_7GcCauseENS0_13CollectorTypeE",
                    "cannot find symbol art::gc::ScopedGCCriticalSection()")
        load_symbol(mirror::sgc_destructor,
                    void(*)(void * ),
                    "_ZN3art2gc23ScopedGCCriticalSectionD1Ev",
                    "cannot find symbol art::gc::~ScopedGCCriticalSection()")
    }

    if (android_version_ > __ANDROID_API_Q__) {
        mirror::ReadWriteMutex **lock_sym;
        // 获取读写锁的地址
        load_symbol(lock_sym,
                    mirror::ReadWriteMutex **,
                    "_ZN3art5Locks13mutator_lock_E",
                    "cannot find symbol art::Locks::mutator_lock_")
        mutator_lock_ = *lock_sym;

        // 获取 exclusive_lock() 方法
        load_symbol(mirror::exclusive_lock,
                    void(*)(void * , mirror::Thread *),
                    "_ZN3art17ReaderWriterMutex13ExclusiveLockEPNS_6ThreadE",
                    "cannot find symbol art::ReaderWriterMutex::ExclusiveLock()")
        // 获取 exclusive_unlock() 方法
        load_symbol(mirror::exclusive_unlock,
                    void(*)(void * , mirror::Thread *),
                    "_ZN3art17ReaderWriterMutex15ExclusiveUnlockEPNS_6ThreadE",
                    "cannot find symbol art::ReaderWriterMutex::ExclusiveUnlock()")
    }

    if (android_version_ > __ANDROID_API_Q__) {
        // 虚拟机的恢复和暂停 
        load_symbol(suspend_all_ptr_,
                    void*,
                    "_ZN3art16ScopedSuspendAllC1EPKcb",
                    "cannot find symbol art::ScopedSuspendAll()")
        load_symbol(resume_all_ptr_,
                    void*,
                    "_ZN3art16ScopedSuspendAllD1Ev",
                    "cannot find symbol art::~ScopedSuspendAll()")
    } else {
        // 虚拟机的恢复和暂停 
        load_symbol(suspend_all_ptr_,
                    void*,
                    "_ZN3art3Dbg9SuspendVMEv",
                    "cannot find symbol art::Dbg::SuspendVM()")
        load_symbol(resume_all_ptr_,
                    void*,
                    "_ZN3art3Dbg8ResumeVMEv",
                    "cannot find symbol art::Dbg::ResumeVM()")
    }

    ds_clean(art_lib);
    return true;

    on_error:
    ds_close(art_lib);
    return false;
}
  1. 首先获取虚拟机动态链接库 libart.so 的地址。
  2. 获取 dump 内存的方法 art::hprof::DumpHeap() 的符号地址,对应的符号是 _ZN3art5hprof8DumpHeapEPKcib
  3. 获取 ScopedGCCriticalSection 类的构造函数和析构函数的符号地址,对应的符号分别是 _ZN3art2gc23ScopedGCCriticalSectionC1EPNS_6ThreadENS0_7GcCauseENS0_13CollectorTypeE_ZN3art2gc23ScopedGCCriticalSectionD1EvAndroid 10 以后的版本,不包含 Android 10)。
  4. 获取 art::Locks::mutator_lock_ 锁的地址,对应的符号是 _ZN3art5Locks13mutator_lock_EAndroid 10 以后的版本,不包含 Android 10)。
  5. 获取 art::ReaderWriterMutex::ExclusiveLock() 方法和 art::ReaderWriterMutex::ExclusiveUnlock() 方法的符号地址,对应的符号分别是 _ZN3art17ReaderWriterMutex13ExclusiveLockEPNS_6ThreadE_ZN3art17ReaderWriterMutex15ExclusiveUnlockEPNS_6ThreadEAndroid 10 以后的版本,不包含 Android 10)。
  6. 获取暂停和恢复虚拟机的方法分为 Android 10 以后的版本和其他版本。Android 10 以后的版本对应的方法是 art::ScopedSuspendAll()art::~ScopedSuspendAll(),对应的符号分别是 _ZN3art16ScopedSuspendAllC1EPKcb_ZN3art16ScopedSuspendAllD1EvAndroid 10 及其以前的版本对应的方法是 art::Dbg::SuspendVM()art::Dbg::ResumeVM(),对应的符号分别是 _ZN3art3Dbg9SuspendVMEv_ZN3art3Dbg8ResumeVMEv

拿到上面的需要的这些方法的地址后就供后续流程使用,如果不是看 Matrix 的源码,我们要怎么获取这些方法的符号呢??就只有去看 Android 的源码和直接在 libart.so 的符号表中找了。

OK 在看完获取 libart.so 中的某些符号地址后,我们继续看 forkDumpAndAnalyze() 方法的实现:

C++ 复制代码
extern "C"
JNIEXPORT jint JNICALL
Java_com_tencent_matrix_resource_MemoryUtil_forkDumpAndAnalyze(JNIEnv *env, jobject,
                                                               jstring java_hprof_path,
                                                               jstring java_result_path,
                                                               jstring java_reference_key,
                                                               jlong timeout) {
    const std::string hprof_path = extract_string(env, java_hprof_path);
    const std::string result_path = extract_string(env, java_result_path);
    const std::string reference_key = extract_string(env, java_reference_key);

    int task_pid = fork_task("matrix_mem_d&a", timeout);
    if (task_pid != 0) {
        // 主进程
        return task_pid;
    } else {
        // dump 进程
        /* dump */
        // 执行 dump
        execute_dump(hprof_path.c_str());
        /* analyze */
        // 分析 dump 文件,找到泄漏的路径
        const std::optional<std::vector<LeakChain>> result =
                execute_analyze(hprof_path.c_str(), reference_key.c_str());
        if (!result.has_value()) _exit(TC_ANALYZE_ERROR);
        /* serialize result */
        // 将泄漏的结果写入到文件中(这是一个自定义的文件格式)
        const bool serialized = execute_serialize(result_path.c_str(), result.value());
        if (!serialized) _exit(TC_SERIALIZE_ERROR);
        /* end */
        // 结束进程
        _exit(TC_NO_ERROR);
    }
}

如果对 Linuxfork 机制一点都不懂的同学,建议先去了解一下相关的资料哈。首先调用 fork_task() 方法执行 fork,如果是主进程就直接返回了;如果是 dump 进程就执行调用 execute_dump() 方法执行 dump 操作,然后调用 execute_analyze() 方法,执行泄漏分析,再然后将泄漏链写入到文件中,然后退出进程。

我看来看看 fork_task() 方法的实现:

C++ 复制代码
static int fork_task(const char *task_name, unsigned int timeout) {
    auto *thread = current_thread();
    // 暂停虚拟机
    suspend_runtime(thread);
    // fork
    int pid = fork();
    if (pid == 0) {
        // dump 进程
        task_process = true;
        if (timeout != 0) {
            alarm(timeout);
        }
        prctl(PR_SET_NAME, task_name);
    } else {
        // 主进程

        // 恢复虚拟机
        resume_runtime(thread);
    }
    return pid;
}

首先通过 suspend_runtime() 方法暂停虚拟机,然后执行 fork() 操作,主进程在 fork() 完成后就调用 resume_runtime() 方法恢复虚拟机。

C++ 复制代码
/**
 * Points to symbol `art::Dbg::SuspendVM()`.
 */
static void *suspend_all_ptr_ = nullptr;

void suspend_runtime(mirror::Thread *thread) {
    if (android_version_ > __ANDROID_API_Q__) {
        mirror::ScopedGCCriticalSection sgc(thread, kGcCauseHprof, kCollectorTypeHprof);
        reinterpret_cast<void (*)(mirror::ScopedSuspend *, const char *, bool)>
        (suspend_all_ptr_)(&suspend_, "matrix_dump_hprof", true);
        mutator_lock_->ExclusiveUnlock(thread);
    } else {
        reinterpret_cast<void (*)()>(suspend_all_ptr_)();
    }
}

暂停虚拟机,如果是 Android 10 及其以前的版本就直接调用上面拿到的 art::Dbg::SuspendVM() 方法地址就行了;Android 10 以后的版本就要麻烦一点了,首先会创建 ScopedGCCriticalSection 的实例,它的构造函数和析构函数的地址前面都拿到了,然后分别再调用 art::ScopedSuspendAll()art::Locks::mutator_lock_->exclusive_unlock() 方法,这两个方法的地址前面也拿到了。

再看看 resume_runtime() 方法的实现:

C++ 复制代码
void resume_runtime(mirror::Thread *thread) {
    if (android_version_ > __ANDROID_API_Q__) {
        mutator_lock_->ExclusiveLock(thread);
        reinterpret_cast<void (*)(mirror::ScopedSuspend *)>(resume_all_ptr_)(&suspend_);
    } else {
        reinterpret_cast<void (*)()>(resume_all_ptr_)();
    }
}

也是朴实无华的代码,Android 10 及其以下的版本,直接调用前面获取到的 art::Dbg::ResumeVM() 的方法就好了;Android 10 以上的版本就需要先调用 art::Locks::mutator_lock_->exclusive_lock() 然后再调用 art::~ScopedSuspendAll()

我们继续看看 dump 进程中的 execute_dump() 方法是如何工作的:

C++ 复制代码
// ! execute on task process
static void execute_dump(const char *file_name) {
    _info_log(TAG, "task_process %d: dump", getpid());
    update_task_state(TS_DUMP);
    dump_heap(file_name);
}

void dump_heap(const char *file_name) {
    dump_heap_(file_name, -1, false);
}

代码也非常简单,获取前面获取到的 art::hprof::DumpHeap() 方法的符号地址,然后直接调用方法就好了。

然后就是通过 execute_analyze() 方法解析 HPROF 文件,找到泄漏的引用路径,这个方法也就是 ResourcePlugin 插件中最最核心的方法了,如果不懂 HPROF 文件格式的同学可以看看我前面写的文章:Android HPROF 内存快照文件详解,后面具体解析 HPROF 的细节代码就不多聊了。

C++ 复制代码
static std::optional<std::vector<LeakChain>>
execute_analyze(const char *hprof_path, const char *reference_key) {
    _info_log(TAG, "task_process %d: analyze", getpid());

    update_task_state(TS_ANALYZER_CREATE);
    // 打开 hprof 文件
    const int hprof_fd = open(hprof_path, O_RDONLY);
    if (hprof_fd == -1) {
        std::stringstream error_builder;
        error_builder << "invoke open() failed on HPROF with errno " << errno;
        on_error(error_builder.str().c_str());
        return std::nullopt;
    }
    HprofAnalyzer::SetErrorListener(analyzer_error_listener);
    // 初始化 HprofAnalyzer 对象
    HprofAnalyzer analyzer(hprof_fd);

    update_task_state(TS_ANALYZER_INITIALIZE);
    // 添加不需要计算泄漏的类
    if (!exclude_default_references(analyzer)) {
        on_error("exclude default references rules failed");
        return std::nullopt;
    }

    update_task_state(TS_ANALYZER_EXECUTE);
    // 执行分析 HPROF 文件和查找泄漏的对象
    return analyzer.Analyze([reference_key](const HprofHeap &heap) {
         // 该 Lambada 是查找 heap 中的泄漏的对象,heap 中保存了解析的 HPROF 文件中的内容。  
    
        // HPROF 解析的结果存放在 heap 中

        // 查找 DestroyedActivityInfo 类在 HPROF 中的 id,它是存放 activity 弱引用的类
        const object_id_t leak_ref_class_id = unwrap_optional(
                heap.FindClassByName(
                        "com.tencent.matrix.resource.analyzer.model.DestroyedActivityInfo"),
                return std::vector<object_id_t>());
        std::vector<object_id_t> leaks;
        // 找到 DestroyedActivityInfo 所有的实例
        for (const object_id_t leak_ref: heap.GetInstances(leak_ref_class_id)) {
            // 找到 DestroyedActivityInfo#mKey 引用
            const object_id_t key_string_id = unwrap_optional(
                    heap.GetFieldReference(leak_ref, "mKey"), continue);
            // 解析 mKey 对应的 String 实例,并解析它。
            const std::string &key_string = unwrap_optional(
                    heap.GetValueFromStringInstance(key_string_id), continue);
            if (key_string != reference_key)
                continue;
            // 找到 Activity 的弱引用
            const object_id_t weak_ref = unwrap_optional(
                    heap.GetFieldReference(leak_ref, "mActivityRef"), continue);
            // 找到 Activity 的引用
            const object_id_t leak = unwrap_optional(
                    heap.GetFieldReference(weak_ref, "referent"), continue);
            leaks.emplace_back(leak);
        }
        return leaks;
    });
}

继续看看 HprofAnalyzerImpl::Analyze() 方法的实现:

C++ 复制代码
std::vector<LeakChain>
HprofAnalyzerImpl::Analyze(
        const std::function<std::vector<object_id_t>(const HprofHeap &)> &leak_finder) {
    internal::heap::Heap heap;
    // 其中这个 data_ 中就是存放 HPROF 文件的字节数组,是通过 mmap 内存映射的
    internal::reader::Reader reader(reinterpret_cast<const uint8_t *>(data_), data_size_);
    // 解析 HPROF,结果都存放在 heap 中
    parser_->Parse(reader, heap, exclude_matcher_group_);
    // 通过 lambda 回调,查找泄漏的对象
    return Analyze(heap, leak_finder(HprofHeap(new HprofHeapImpl(heap))));
}

看看 Parse() 方法的实现:

Kotlin 复制代码
void
HeapParserEngineImpl::Parse(reader::Reader &reader, heap::Heap &heap,
                            const ExcludeMatcherGroup &exclude_matcher_group,
                            const HeapParserEngine &next) const {
    // 解析 Header                        
    next.ParseHeader(reader, heap);

    while (true) {
        const uint8_t tag = reader.ReadU1();
        reader.SkipU4(); // Skip timestamp.
        const uint32_t length = reader.ReadU4();
        switch (tag) {
            case tag::kStrings:
                // 解析 String 的 Record
                next.ParseStringRecord(reader, heap, length);
                break;
            case tag::kLoadClasses:
                // 解析 LoadClass 的 Record
                next.ParseLoadClassRecord(reader, heap, length);
                break;
            case tag::kHeapDump:
            case tag::kHeapDumpSegment:
                // 解析 Dump 的 Record
                next.ParseHeapContent(reader, heap, length, exclude_matcher_group, next);
                break;
            case tag::kHeapDumpEnd:
                // 结束
                goto break_read_loop;
            default:
                // 跳过其他不感兴趣的 Record
                reader.Skip(length);
                break;
        }
    }
    break_read_loop:
    // 做一些还没有解析的内容,主要处理实例的成员 Field 中的内容和找到 Exclude 的 GCRoot
    next.LazyParse(heap, exclude_matcher_group);
}

HPROF 文件的解析内容放在 Heap 中,其中它只解析 StringLoadClassHeapDumpHeapDumpSegmentRecord,其他的 Record 直接跳过,在解析完基础的 Records 后再去解析所有的实例中引用类型的 Field。 看看 HeapParserEngine#ParseHeapContent() 方法是如何处理 HeapDumpHeapDumpSegment 的子 Record 的。

C++ 复制代码
void HeapParserEngineImpl::ParseHeapContent(reader::Reader &reader, heap::Heap &heap,
                                            size_t record_length,
                                            const ExcludeMatcherGroup &exclude_matcher_group,
                                            const HeapParserEngine &next) const {
    size_t bytes_read = 0;
    while (bytes_read < record_length) {
        const uint8_t tag = reader.ReadU1();
        bytes_read += sizeof(uint8_t);
        switch (tag) {
            case tag::kHeapRootUnknown:
                bytes_read += next.ParseHeapContentRootUnknownSubRecord(reader, heap);
                break;
            case tag::kHeapRootJniGlobal:
                bytes_read += next.ParseHeapContentRootJniGlobalSubRecord(reader, heap);
                break;
            case tag::kHeapRootJniLocal:
                bytes_read += next.ParseHeapContentRootJniLocalSubRecord(reader, heap);
                break;
            case tag::kHeapRootJavaFrame:
                bytes_read += next.ParseHeapContentRootJavaFrameSubRecord(reader, heap);
                break;
            case tag::kHeapRootNativeStack:
                bytes_read += next.ParseHeapContentRootNativeStackSubRecord(reader, heap);
                break;
            case tag::kHeapRootStickyClass:
                bytes_read += next.ParseHeapContentRootStickyClassSubRecord(reader, heap);
                break;
            case tag::kHeapRootThreadBlock:
                bytes_read += next.ParseHeapContentRootThreadBlockSubRecord(reader, heap);
                break;
            case tag::kHeapRootMonitorUsed:
                bytes_read += next.ParseHeapContentRootMonitorUsedSubRecord(reader, heap);
                break;
            case tag::kHeapRootThreadObject:
                bytes_read += next.ParseHeapContentRootThreadObjectSubRecord(reader, heap);
                break;
            case tag::kHeapRootInternedString:
                bytes_read += next.ParseHeapContentRootInternedStringSubRecord(reader, heap);
                break;
            case tag::kHeapRootFinalizing:
                bytes_read += next.ParseHeapContentRootFinalizingSubRecord(reader, heap);
                break;
            case tag::kHeapRootDebugger:
                bytes_read += next.ParseHeapContentRootDebuggerSubRecord(reader, heap);
                break;
            case tag::kHeapRootReferenceCleanup:
                bytes_read += next.ParseHeapContentRootReferenceCleanupSubRecord(reader, heap);
                break;
            case tag::kHeapRootVMInternal:
                bytes_read += next.ParseHeapContentRootVMInternalSubRecord(reader, heap);
                break;
            case tag::kHeapRootJniMonitor:
                bytes_read += next.ParseHeapContentRootJniMonitorSubRecord(reader, heap);
                break;
            case tag::kHeapRootUnreachable:
                bytes_read += next.ParseHeapContentRootUnreachableSubRecord(reader, heap);
                break;
            case tag::kHeapClassDump:
                bytes_read += next.ParseHeapContentClassSubRecord(reader, heap,
                                                                  exclude_matcher_group);
                break;
            case tag::kHeapInstanceDump:
                bytes_read += next.ParseHeapContentInstanceSubRecord(reader, heap);
                break;
            case tag::kHeapObjectArrayDump:
                bytes_read += next.ParseHeapContentObjectArraySubRecord(reader, heap);
                break;
            case tag::kHeapPrimitiveArrayDump:
                bytes_read += next.ParseHeapContentPrimitiveArraySubRecord(reader, heap);
                break;
            case tag::kHeapPrimitiveArrayNoDataDump:
                bytes_read += next.ParseHeapContentPrimitiveArrayNoDataDumpSubRecord(reader,
                                                                                     heap);
                break;
            case tag::kHeapDumpInfo:
                bytes_read += next.SkipHeapContentInfoSubRecord(reader, heap);
                break;
            default:
                std::stringstream error_builder;
                error_builder << "unsupported heap dump tag " << std::to_string(tag);
                pub_fatal(error_builder.str());
        }
    }
}

它处理了所有的 HPROF 中的 SubRecord。具体的细节就不看了,也是保存在 Heap 中。

到这里解析 HPROF 文件就全部完了,然后我们再看看如何构建泄漏链,对应的方法是 HprofAnalyzerImpl::Analyze()

C++ 复制代码
std::vector<LeakChain> HprofAnalyzerImpl::Analyze(const internal::heap::Heap &heap,
                                                  const std::vector<object_id_t> &leaks) {
    // Key 是泄漏对象的 ID;Value 也是一个 Map:Key 是 GCRoot 的 ID,Value 是到达泄漏对象的路径。
    const auto chains = ({
        const HprofHeap hprof_heap(new HprofHeapImpl(heap));
        // 找到泄漏链
        internal::analyzer::find_leak_chains(heap, leaks);
    });
    std::vector<LeakChain> result;
    for (const auto&[_, chain]: chains) {
        // 构建泄漏链
        const std::optional<LeakChain> leak_chain = BuildLeakChain(heap, chain);
        if (leak_chain.has_value()) result.emplace_back(leak_chain.value());
    }
    return std::move(result);
}

其中上面的代码中 leaks 表示泄漏的对象,是我们前面代码的 Lambda 表达式获取的,代码可以再往上翻翻,代码注释我写得很详细。我们再看看 internal::analyzer::find_leak_chains() 方法:

C++ 复制代码
std::map<heap::object_id_t, std::vector<std::pair<heap::object_id_t, std::optional<heap::reference_t>>>>
find_leak_chains(const heap::Heap &heap, const std::vector<heap::object_id_t> &tracked) {

    /* Use Breadth-First Search algorithm to find all the references to tracked objects. */

    std::map<heap::object_id_t, std::vector<std::pair<heap::object_id_t, std::optional<heap::reference_t>>>> ret;
    // 遍历泄漏的对象
    for (const auto &leak: tracked) {
        // 已经遍历过的节点, Key 对象 ID;Value 是引用它的节点信息,在 LeakCanary 中的命名为 dominator
        std::map<heap::object_id_t, ref_node_t> traversed;
        // 等待查找的节点
        std::deque<ref_node_t> waiting;

        // 添加所有的 GCRoot 到等待查找的对象
        for (const heap::object_id_t gc_root: heap.GetGcRoots()) {
            ref_node_t node = {
                    .referent_id = gc_root,
                    .super = std::nullopt,
                    .depth = 0
            };
            traversed[gc_root] = node;
            waiting.push_back(node);
        }

        bool found = false;
        while (!waiting.empty()) {
            // 取出一个需要查找的节点
            const ref_node_t node = waiting.front();
            waiting.pop_front();

            const heap::object_id_t referrer_id = node.referent_id;
            if (heap.GetLeakReferenceGraph().count(referrer_id) == 0) continue;
            // 遍历节点的实例的所有引用类型的成员 Field
            for (const auto &[referent, reference]: heap.GetLeakReferenceGraph().at(referrer_id)) {
                try {
                    // 如果已经遍历过了,并且 GCRoot 的深度小于当前的深度就跳过,因为我们只需要一条到 GCRoot 最短的路径
                    if (traversed.at(referent).depth <= node.depth + 1) continue;
                } catch (const std::out_of_range &) {}
                // 构建新的节点添加到已经遍历过
                ref_node_t next_node = {
                        .referent_id = referent,
                        .super = ref_super_t{
                                 // 持有该引用的实例ID
                                .referrer_id = referrer_id,
                                // 持有该引用的实例的 Field
                                .reference = reference
                        },
                        .depth = node.depth + 1
                };
                traversed[referent] = next_node;
                // 判断当前节点是否是泄漏的对象,如果是,跳出循环,如果不是添加到下一个需要遍历的节点
                if (leak == referent) {
                    found = true;
                    goto traverse_complete;
                } else {
                    waiting.push_back(next_node);
                }
            }
        }
        traverse_complete:
        if (found) {
            // 已经找到泄漏对象

            // Pair:first 对象的 ID,second 引用该对象的 Field 信息
            ret[leak] = std::vector<std::pair<heap::object_id_t, std::optional<heap::reference_t>>>();
            std::optional<heap::object_id_t> current = leak;
            std::optional<heap::reference_t> current_reference = std::nullopt;
            while (current != std::nullopt) {
                ret[leak].push_back(std::make_pair(current.value(), current_reference));
                const auto &super = traversed.at(current.value()).super;
                if (super.has_value()) {
                    current = super.value().referrer_id;
                    current_reference = super.value().reference;
                } else {
                    current = std::nullopt;
                }
            }
            // 反向,让 GCRoot 排在前面
            std::reverse(ret[leak].begin(), ret[leak].end());
        }
    }

    return std::move(ret);
}

总的逻辑是:从所有的 GCRoot 开始往下查找,需要查找的对象放在 waiting 本地变量中,已经遍历过的节点放在 traversed 中,Key 对象 IDValue 是引用它的节点信息,在 LeakCanary 中的命名为 dominator。遍历 waiting 中的对象,直到它变成空,或者已经找到目标的泄漏对象,查询对象引用的其他对象是通过 heap.GetLeakReferenceGraph().at() 方法,它能够返回目标对象所引用的其他对象,而且还包含 Field 的信息,在其所引用的其他对象中,如果已经遍历过了,并且 GCRoot 的深度小于当前的深度就跳过,因为我们只需要一条到 GCRoot 最短的路径。如果不需要跳过,就检查是不是我们需要的泄漏对象,如果是我们要找的泄漏对象就退出循环,如果不是我们要找的泄漏的对象就添加到 traversed 中。

上面找到的泄漏信息又会被封装在 LeakChain 中,对应的方法是 HprofAnalyzerImpl::BuildLeakChain()

C++ 复制代码
std::optional<LeakChain> HprofAnalyzerImpl::BuildLeakChain(const internal::heap::Heap &heap,
                                                           const std::vector<std::pair<internal::heap::object_id_t, std::optional<internal::heap::reference_t>>> &chain) {
    if (chain.empty()) return std::nullopt;
    std::optional<LeakChain::GcRoot> gc_root;
    std::vector<LeakChain::Node> nodes;
    internal::heap::reference_t next_reference{};
    for (size_t i = 0; i < chain.size(); ++i) {
        const auto &chain_node = chain[i];
        // 获取 Class 的名字
        const std::string referent = ({
            const object_id_t class_id = (chain_node.second.has_value() &&
                                          chain_node.second.value().type ==
                                          internal::heap::kStaticField)
                                         ? chain_node.first
                                         : unwrap(heap.GetClass(chain_node.first),
                                                  return std::nullopt);
            unwrap(heap.GetClassName(class_id), return std::nullopt);
        });
        if (i == 0) {
            // GCRoot
            const LeakChain::GcRoot::Type gc_root_type =
                    convert_gc_root_type(heap.GetGcRootType(chain_node.first));
            gc_root = create_leak_chain_gc_root(referent, gc_root_type);
        } else {
            // 非 GCRoot

            // Field 的名字
            const std::string reference = next_reference.type == internal::heap::kArrayElement
                                          ? std::to_string(next_reference.index)
                                          : heap.GetString(
                            next_reference.field_name_id).value_or("");
            // Field 的类型
            const LeakChain::Node::ReferenceType reference_type =
                    convert_reference_type(next_reference.type);
            // 引用实例的类型
            const LeakChain::Node::ObjectType referent_type =
                    convert_object_type(heap.GetInstanceType(chain_node.first));
            nodes.emplace_back(
                    create_leak_chain_node(reference, reference_type, referent, referent_type));
        }
        next_reference = unwrap(chain_node.second, break);
    }
    return create_leak_chain(gc_root.value(), nodes);
}

LeakChain 中会保存 GCRoot 和泄漏的路径;路径的节点用 LeakChain::Node 封装,其中包含实例对应的 Class 名字,Field 的名字,Field 的类型,Field 引用的实例的类型。

在找到泄漏链后就会序列化写入到文件中,在写入文件完成后,Kotlin 的代码就会反序列化读取器中的内容,然后上报泄漏链,Bug 就发生在 Kotlin 代码中读取泄漏链的过程中。

我们先看序列化的代码:

C++ 复制代码
// ! execute on task process
static bool execute_serialize(const char *result_path, const std::vector<LeakChain> &leak_chains) {
    _info_log(TAG, "task_process %d: serialize", getpid());

    update_task_state(TS_CREATE_RESULT_FILE);
    bool result = false;
    int result_fd = open(result_path, O_WRONLY | O_CREAT | O_TRUNC, S_IWUSR);
    if (result_fd == -1) {
        std::stringstream error_builder;
        error_builder << "invoke open() failed on result file with errno " << errno;
        on_error(error_builder.str().c_str());
        return false;
    }

    update_task_state(TS_SERIALIZE);
    // See comment documentation of <code>MemoryUtil.deserialize</code> for the file format of
    // result file.
#define write_data(content, size)                                                           \
        if (write(result_fd, content, size) == -1) {                                        \
            std::stringstream error_builder;                                                \
            error_builder << "invoke write() failed on result file with errno " << errno;   \
            on_error(error_builder.str().c_str());                                          \
            goto write_leak_chain_done;                                                     \
        }

    const uint32_t byte_order_magic = 0x1;
    const uint32_t leak_chain_count = leak_chains.size();
    // 前 4 个字节固定为 1
    write_data(&byte_order_magic, sizeof(uint32_t))
    // 4 个字节表示泄漏链的长度
    write_data(&leak_chain_count, sizeof(uint32_t))
    // 遍历所有的泄漏链,一个泄漏链表示一个泄漏的对象
    for (const auto &leak_chain : leak_chains) {
        const uint32_t leak_chain_length = leak_chain.GetDepth() + 1;
        // 4 个字节泄漏链的深度
        write_data(&leak_chain_length, sizeof(uint32_t))

        int32_t gc_root_type;
        if (leak_chain_length == 1) {
            // 如果链的长度为 1 就表示只有 GCRoot 一个节点
            gc_root_type = serialize_object_type(LeakChain::Node::ObjectType::kInstance);
        } else {
            // 获取 GCRoot 持有的第一个引用的引用类型
            const auto ref_type = leak_chain.GetNodes()[0].GetReferenceType();
            switch (ref_type) {
                case LeakChain::Node::ReferenceType::kStaticField:
                    gc_root_type = serialize_object_type(LeakChain::Node::ObjectType::kClass);
                    break;
                case LeakChain::Node::ReferenceType::kInstanceField:
                    gc_root_type = serialize_object_type(
                            LeakChain::Node::ObjectType::kInstance);
                    break;
                case LeakChain::Node::ReferenceType::kArrayElement:
                    gc_root_type = serialize_object_type(
                            LeakChain::Node::ObjectType::kObjectArray);
                    break;
            }
        }
        // 4 个字节表示 GCRoot 的类型
        write_data(&gc_root_type, sizeof(int32_t))
        const uint32_t gc_root_name_length = leak_chain.GetGcRoot().GetName().length();
        // 4 个字节表示 GCRoot 类型名字的的长度
        write_data(&gc_root_name_length, sizeof(uint32_t))
        // 写入 GCRoot 类型名字
        write_data(leak_chain.GetGcRoot().GetName().c_str(), gc_root_name_length)

        // 遍历泄漏链接的节点
        for (const auto &node : leak_chain.GetNodes()) {
            // 获取引用类型
            const int32_t serialized_reference_type =
                    serialize_reference_type(node.GetReferenceType());
            // 4 个字节表示引用类型
            write_data(&serialized_reference_type, sizeof(int32_t))
            const uint32_t reference_name_length = node.GetReference().length();
            // 4 个字节表示引用类型名字长度
            write_data(&reference_name_length, sizeof(uint32_t))
            // 写入引用类型名字
            write_data(node.GetReference().c_str(), reference_name_length)

            const int32_t serialized_object_type =
                    serialize_object_type(node.GetObjectType());
            // 4 个字节表示实例类型
            write_data(&serialized_object_type, sizeof(int32_t))
            const uint32_t object_name_length = node.GetObject().length();
            // 4 个字节表示实例类型名字的长度
            write_data(&object_name_length, sizeof(uint32_t))
            // 写入实例类型的名字
            write_data(node.GetObject().c_str(), object_name_length)
        }

        const uint32_t end_tag = 0;
        // 4 个字节表示写入单个泄漏链完毕
        write_data(&end_tag, sizeof(uint32_t))
    }
    result = true;
    write_leak_chain_done:
    close(result_fd);
    return result;
}

上面代码比较简单,我也写了注释,看着应该没压力。

我们再来看 Kotlin 中反序列化的代码:

Kotlin 复制代码
private fun deserialize(file: File): List<LeakChain> {
    val stream = run {
        val input = file.inputStream()
        val magic = ByteArray(4)
        input.read(magic, 0, 4)
        // 确定大端或者小端
        if (magic.contentEquals(byteArrayOf(0x00, 0x00, 0x00, 0x01)))
            OrderedStreamWrapper(ByteOrder.BIG_ENDIAN, input)
        else
            OrderedStreamWrapper(ByteOrder.LITTLE_ENDIAN, input)
    }
    try {
        // 读取泄漏引用链数量
        val chainCount = stream.readOrderedInt()
        if (chainCount == 0) {
            stream.close()
            return emptyList()
        }

        val result = mutableListOf<LeakChain>()
        for (chainIndex in 0 until chainCount) {
            val nodes = mutableListOf<LeakChain.Node>()
            // 链的节点数量
            val chainLength = stream.readOrderedInt()

            // TODO:这里貌似有一个 BUG,少了读取 GCRoot 的类型和 GCRoot 的名字.
            // 1. 4 个字节 GCRoot 类型
            // 2. 4 个字节 GCRoot 类型名字的长度
            // 3. GC Root 类型的名字

            for (nodeIndex in 0 until chainLength) {

                // 1. 4 个字节引用类型
                // 2. 4 个字节引用类型名字长度
                // 3. 引用类型名字

                // 4. 4 个字节实例类型
                // 5. 4 个字节实例类型名字长度
                // 6. 实例类型名字

                // node
                val objectType = stream.readOrderedInt()
                val objectName = stream.readString(stream.readOrderedInt())

                // reference
                val referenceType = stream.readOrderedInt()
                val referenceName =
                    if (referenceType == 0) ""  // reached end tag
                    else stream.readString(stream.readOrderedInt())

                nodes += LeakChain.Node(objectName, objectType, referenceName, referenceType)
            }
            result += LeakChain(nodes)
        }
        return result
    } catch (exception: IOException) {
        throw exception
    } finally {
        stream.close()
    }
}

上面 Bug 的位置我已经标记出来了,每个链写入的时候会写入 GCRoot 的信息,但是 Kotlin 的代码中没有读这个数据,就会导致后面的数据读取发生一个错位。也就是后面节点中读取引用类型和引用的实例类型信息发生错位。

最后

ResourcePlugin 的代码中还有裁剪 HPROF 的代码,我就不再分析源码了,我这里可以在讲讲它裁剪的方案,供大家参考。ResourcePlugin 主要裁剪的是 PrimaryArray 类型的实例,只保留 StringBitmap 中的 PrimaryArray,重复的 BitmapPrimaryArray 也会被裁剪掉。

相关推荐
Tans53 天前
Androidx Fragment 源码阅读笔记(下)
android jetpack·源码阅读
Tans56 天前
Androidx Fragment 源码阅读笔记(上)
android jetpack·源码阅读
Tans510 天前
Androidx Lifecycle 源码阅读笔记
android·android jetpack·源码阅读
凡小烦10 天前
LeakCanary源码解析
源码阅读·leakcanary
程序猿阿越18 天前
Kafka源码(四)发送消息-服务端
java·后端·源码阅读
CYRUS_STUDIO21 天前
Android 源码如何导入 Android Studio?踩坑与解决方案详解
android·android studio·源码阅读
Code_Artist21 天前
[Java并发编程]6.并发集合类:ConcurrentHashMap、CopyOnWriteArrayList
java·后端·源码阅读
Joey_Chen1 个月前
【源码赏析】开源C++日志库spdlog
架构·源码阅读
顾林海1 个月前
Android MMKV 深度解析:原理、实践与源码剖析
android·面试·源码阅读
程序猿阿越1 个月前
Kafka源码(三)发送消息-客户端
java·后端·源码阅读