• 从Matrix-ResourceCanary看内存快照生成-ForkAnalyseProcessor(1)


    前文看到AutoDumpProcessor的处理逻辑主要是生成,裁剪hprof文件并回调到PluginListener中,接下来我们来看下ForkAnalyseProcessor的处理逻辑。

    ForkAnalyseProcessor

    public class ForkAnalyseProcessor extends BaseLeakProcessor {
    
        private static final String TAG = "Matrix.LeakProcessor.ForkAnalyse";
    
        public ForkAnalyseProcessor(ActivityRefWatcher watcher) {
            super(watcher);
        }
    
        @Override
        public boolean process(DestroyedActivityInfo destroyedActivityInfo) {
            ......
            getWatcher().triggerGc();
    
            if (dumpAndAnalyse(
                    destroyedActivityInfo.mActivityName,
                    destroyedActivityInfo.mKey
            )) {
                getWatcher().markPublished(destroyedActivityInfo.mActivityName, false);
                return true;
            }
            return false;
        }
    
        private boolean dumpAndAnalyse(String activity, String key) {
    
            /* Dump */
    
            final long dumpStart = System.currentTimeMillis();
    
            File hprof = null;
            try {
                hprof = HprofFileManager.INSTANCE.prepareHprofFile("FAP", true);
            } catch (FileNotFoundException e) {
                MatrixLog.printErrStackTrace(TAG, e, "");
            }
    
            if (hprof != null) {
                if (!MemoryUtil.dump(hprof.getPath(), 600)) {
                    MatrixLog.e(TAG, String.format("heap dump for further analyzing activity with key [%s] was failed, just ignore.",
                            key));
                    return false;
                }
            }
    
            if (hprof == null || hprof.length() == 0) {
                publishIssue(
                        SharePluginInfo.IssueType.ERR_FILE_NOT_FOUND,
                        ResourceConfig.DumpMode.FORK_ANALYSE,
                        activity, key, "FileNull", "0");
                MatrixLog.e(TAG, "cannot create hprof file");
                return false;
            }
    
            MatrixLog.i(TAG, String.format("dump cost=%sms refString=%s path=%s",
                    System.currentTimeMillis() - dumpStart, key, hprof.getPath()));
    
            /* Analyse */
    
            try {
                final long analyseStart = System.currentTimeMillis();
    
                final ActivityLeakResult leaks = analyze(hprof, key);
                MatrixLog.i(TAG, String.format("analyze cost=%sms refString=%s",
                        System.currentTimeMillis() - analyseStart, key));
    
                if (leaks.mLeakFound) {
                    final String leakChain = leaks.toString();
                    publishIssue(
                            SharePluginInfo.IssueType.LEAK_FOUND,
                            ResourceConfig.DumpMode.FORK_ANALYSE,
                            activity, key, leakChain,
                            String.valueOf(System.currentTimeMillis() - dumpStart));
                    MatrixLog.i(TAG, leakChain);
                } else {
                    MatrixLog.i(TAG, "leak not found");
                }
    
            } catch (OutOfMemoryError error) {
                publishIssue(
                        SharePluginInfo.IssueType.ERR_ANALYSE_OOM,
                        ResourceConfig.DumpMode.FORK_ANALYSE,
                        activity, key, "OutOfMemoryError",
                        "0");
                MatrixLog.printErrStackTrace(TAG, error.getCause(), "");
            } finally {
                //noinspection ResultOfMethodCallIgnored
                hprof.delete();
            }
    
            /* Done */
    
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

    从上述代码可以看到在ForkAnalyseProcessor中,主要是通过dumpAndAnalyse来处理发现的内存泄漏问题,在该函数内,主要分为以下几步:

    1. prepareHprofFile:创建hprof文件
    2. MemoryUtil.dump:生成hprof文件内容
    3. analyze:分析hprof文件
    4. publishIssue:报告问题
    5. hprof.delete():删除hprof文件
    prepareHprofFile

    在HprofFileManager.INSTANCE.prepareHprofFile主要是进行hprof文件的预创建工作,包含清理历史文件,确保有足够的存储空间,判断存储空间是否可用,拼接hprof文件名等操作,这里预创建的hprof文件并没有数据内容,prepareHprofFile实现代码如下:

    @Throws(FileNotFoundException::class)
    fun prepareHprofFile(prefix: String = "", deleteSoon: Boolean = false): File {
        hprofStorageDir.prepare(deleteSoon)
        return File(hprofStorageDir, getHprofFileName(prefix))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    MemoryUtil.dump

    MemoryUtil.dump函数主要完成了hprof文件的真实内容填充工作,代码如下所示:

    @JvmStatic
    @JvmOverloads
    fun dump(
        hprofPath: String,
        timeout: Long = DEFAULT_TASK_TIMEOUT
    ): Boolean = initSafe { exception ->
        if (exception != null) {
            error("", exception)
            return@initSafe false
        }
        return when (val pid = forkDump(hprofPath, timeout)) {
            -1 -> run {
                error("Failed to fork task executing process.")
                false
            }
            else -> run { // current process
                info("Wait for task process [${pid}] complete executing.")
                val result = waitTask(pid)
                result.exception?.let {
                    info("Task process [${pid}] complete with error: ${it.message}.")
                } ?: info("Task process [${pid}] complete without error.")
                return result.exception == null
            }
        }
    }
    
    private external fun forkDump(hprofPath: String, timeout: Long): Int
    private external fun waitTask(pid: Int): TaskResult
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    可以看到代码中主要逻辑是执行forkDump获取进程id,如果进程id为-1,则返回false,dump失败,如果进程id不为-1,则执行waitTask方法,如果返回的TaskResult对象中没有异常,说明dump成功,否则失败,而forkDump和waitTask都是native方法,接下来我们一起看下这两函数的实现。

    forkDump

    MemoryUtil.dump对应的native实现如下所示:

    extern "C"
    JNIEXPORT jint JNICALL
    Java_com_tencent_matrix_resource_MemoryUtil_forkDump(JNIEnv *env, jobject,
                                                         jstring java_hprof_path,
                                                         jlong timeout) {
        const std::string hprof_path = extract_string(env, java_hprof_path);
    
        int task_pid = fork_task("matrix_mem_dump", timeout);
        if (task_pid != 0) {
            return task_pid;
        } else {
            /* dump生成hprof文件 */
            execute_dump(hprof_path.c_str());
            /* 退出进程 */
            _exit(TC_NO_ERROR);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    static int fork_task(const char *task_name, unsigned int timeout) {
        auto *thread = current_thread();
        // 调用art::Dbg::SuspendVM()暂停进程运行
        suspend_runtime(thread);
        // fork创建进程
        int pid = fork();
        if (pid == 0) {
            task_process = true;
            if (timeout != 0) {
                alarm(timeout);
            }
            // 设置线程名称
            prctl(PR_SET_NAME, task_name);
        } else {
            // 调用art::Dbg::ResumeVM()恢复进程运行
            resume_runtime(thread);
        }
        return pid;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    结合注释,我们可以看出这是一段创建子进程并根据子进程pid运行逻辑的代码,那么这一段代码是怎么执行的呢?

    要了解上述代码怎么执行的,我们首先应该清楚fork函数创建子进程的作用和特点,针对fork创建的子进程而言,其和父进程拥有相同的内存空间,fork函数返回值如下所示:

    image-20230827113118648

    可以看出fork在父进程执行时返回所创建子进程的pid信息,在子进程自身执行时返回0,结合代码,可以得到下图:

    forkDump.drawio

    接下来我们继续来看下子进程execute_dump和_exit的实现。

    execute_dump
    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);
    }
    
    static void (*dump_heap_)(const char *, int, bool) = nullptr;
    
    void dump_heap(const char *file_name) {
        dump_heap_(file_name, -1, false);
    }
    
    // xhook
    load_symbol(dump_heap_,
                    void(*)(const char *, int, bool ),
                    "_ZN3art5hprof8DumpHeapEPKcib",
                    "cannot find symbol art::hprof::DumpHeap()")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    可以看到execute_dump最终调用的是art::hprof::DumpHeap()方法,Debug.dumpHprofData最终也是通过jni调用该方法,生成hprof文件。

    _exit

    image-20230827123154594

    结合文档可以看出_exit函数主要用于停止进程运行。

    waitTask
    extern "C" JNIEXPORT jobject JNICALL
    Java_com_tencent_matrix_resource_MemoryUtil_waitTask(JNIEnv *env, jobject, jint pid) {
        int status;
        // 通过waitpid等待子进程状态通知
        if (waitpid(pid, &status, 0) == -1) {
            _error_log(TAG, "invoke waitpid failed with errno %d", errno);
            return create_task_result(env, TR_TYPE_WAIT_FAILED, errno, TS_UNKNOWN, "none");
        }
    
        const int8_t task_state = get_task_state_and_cleanup(pid);
        const std::string task_error = get_task_error_and_cleanup(pid);
        if (WIFEXITED(status)) {
            return create_task_result(env, TR_TYPE_EXIT, WEXITSTATUS(status), task_state, task_error);
        } else if (WIFSIGNALED(status)) {
            return create_task_result(env, TR_TYPE_SIGNALED, WTERMSIG(status), task_state, task_error);
        } else {
            return create_task_result(env, TR_TYPE_UNKNOWN, 0, task_state, task_error);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    从waitpid阻塞等待获取到子进程退出状态后,将子进程执行结果包装成TaskResult对象返回。

    waitpid

    waitpid说明如下图,可以看到waitpid用于阻塞当前线程执行,直到给定的pid关联的子进程状态发生改变时唤醒,唤醒后说明子进程已退出,查看子进程退出原因,并返回结果给上层,至此MemoryUtil.dump流程结束。

    image-20230827124402500

    image-20230827124551875

  • 相关阅读:
    JAVA计算机毕业设计大学生网络创业就业管理系统计算机(附源码、数据库)
    【Docker】Docker Network(网络)
    海格里斯HEGERLS工程项目案例|陕西西安某新能源电池制造集团企业三期自放电立体库工程项目安装过程
    C语言函数调用栈
    Python速成1——环境搭建与基本数据类型
    ssm+vue的药品管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。
    LeetCode笔记:Biweekly Contest 81
    计算机图形学浅谈与学习心得
    【LeetCode】739 每日温度
    【定制项目】【M15 消防安全宣传】主要模块:视频 + 音频 + 图标 + 问答游戏
  • 原文地址:https://blog.csdn.net/u010132993/article/details/132747268