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 也会被裁剪掉。

相关推荐
Jayconscious7 天前
React源码解析(一):从入口函数调试入手,自下而上窥探react架构
前端·react.js·源码阅读
中草药z12 天前
【Spring】深入解析 Spring 原理:Bean 的多方面剖析(源码阅读)
java·数据库·spring boot·spring·bean·源码阅读
Tans521 天前
LeakCanary 源码阅读笔记(四)
源码阅读·leakcanary
灵感__idea22 天前
Vuejs技术内幕:组件渲染
前端·vue.js·源码阅读
Sword991 个月前
【ThreeJs原理解析】第4期 | 向量
前端·three.js·源码阅读
biubiubiu王大锤1 个月前
Nacos源码分析-永久实例健康检查机制
java·源码阅读
Sword991 个月前
【ThreeJs原理解析】第3期 | 射线检测Raycaster实现原理
前端·three.js·源码阅读
欧阳码农1 个月前
看不懂来打我!Vue3的watch是如何实现数据监听的
vue.js·源码·源码阅读
biubiubiu王大锤2 个月前
nacos源码分析-客户端启动与配置动态更新的实现细节
后端·源码阅读