前文看到AutoDumpProcessor的处理逻辑主要是生成,裁剪hprof文件并回调到PluginListener中,接下来我们来看下ForkAnalyseProcessor的处理逻辑。
ForkAnalyseProcessor
public class ForkAnalyseProcessor extends BaseLeakProcessor {private static final String TAG = "Matrix.LeakProcessor.ForkAnalyse";public ForkAnalyseProcessor(ActivityRefWatcher watcher) {super(watcher);}@Overridepublic 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 ResultOfMethodCallIgnoredhprof.delete();}/* Done */return true;}
}
从上述代码可以看到在ForkAnalyseProcessor中,主要是通过dumpAndAnalyse来处理发现的内存泄漏问题,在该函数内,主要分为以下几步:
- prepareHprofFile:创建hprof文件
- MemoryUtil.dump:生成hprof文件内容
- analyze:分析hprof文件
- publishIssue:报告问题
- 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 processinfo("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函数返回值如下所示:
可以看出fork在父进程执行时返回所创建子进程的pid信息,在子进程自身执行时返回0,结合代码,可以得到下图:
接下来我们继续来看下子进程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
结合文档可以看出_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流程结束。