从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;
    }
}

从上述代码可以看到在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))
}
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

可以看到代码中主要逻辑是执行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);
    }
}
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;
}

结合注释,我们可以看出这是一段创建子进程并根据子进程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()")

可以看到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);
    }
}

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

waitpid

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

image-20230827124402500

image-20230827124551875

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值