前文看到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来处理发现的内存泄漏问题,在该函数内,主要分为以下几步:
- 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 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函数返回值如下所示:
可以看出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流程结束。