前面我们已经了解了fork子进程dump生成hprof文件的机制,相信大家也清楚了,即然能在native层创建子进程进行dump hprof的操作,那么自然也可以在子进程中同时进行hprof文件的解析,此时应该用哪种Processor呢?没错,NativeForkAnalyzeProcessor。
NativeForkAnalyzeProcessor.process
NativeForkAnalyzeProcessor.process的代码如下所示,可以看到主要的流程如下所示:
- HprofFileManager.prepareHprofFile:准备hprof文件
- MemoryUtil.dumpAndAnalyze:生成hprof文件内容并解析
- publishIssue:报告泄漏问题
- hprof.deleteIfExist:删除hprof文件
其中1,3,4我们在前文中已做介绍,不再赘述,接下来看下MemoryUtil.dumpAndAnalyze的实现。
kotlin
override fun process(destroyedActivityInfo: DestroyedActivityInfo): Boolean {
publishIssue(SharePluginInfo.IssueType.LEAK_FOUND, ResourceConfig.DumpMode.NO_DUMP, destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey, "no dump", "0")
val hprof = safeLetOrNull { HprofFileManager.prepareHprofFile("NFAP", true) } ?: run {
publishIssue(
SharePluginInfo.IssueType.LEAK_FOUND,
ResourceConfig.DumpMode.FORK_ANALYSE,
destroyedActivityInfo.mActivityName, "[unknown]", "Failed to create hprof file.", "0"
)
return true
}
val activity = destroyedActivityInfo.mActivityName
val key = destroyedActivityInfo.mKey
watcher.triggerGc()
MatrixLog.i(TAG, "fork dump and analyse")
val result = MemoryUtil.dumpAndAnalyze(hprof.absolutePath, key, timeout = 600)
MatrixLog.i(TAG, "fork dump and analyse done")
if (result.mFailure != null) {
// Copies file to retry repository and analyzes it again when the screen is locked.
MatrixLog.i(TAG, "Process failed, move into retry repository.")
val failure = result.mFailure.toString()
if (retryRepo?.save(hprof, activity, key, failure) != true) {
publishIssue(
SharePluginInfo.IssueType.ERR_EXCEPTION,
ResourceConfig.DumpMode.FORK_ANALYSE,
activity, key, failure,
result.mAnalysisDurationMs.toString()
)
}
} else {
if (result.mLeakFound) {
watcher.markPublished(activity, false)
result.toString().let {
publishIssue(
SharePluginInfo.IssueType.LEAK_FOUND,
ResourceConfig.DumpMode.FORK_ANALYSE,
activity, key, it, result.mAnalysisDurationMs.toString()
)
MatrixLog.i(TAG, "leak found: $it")
}
}
}
hprof.deleteIfExist()
return true
}
MemoryUtil.dumpAndAnalyze
MemoryUtil.dumpAndAnalyze函数实现如上,可以看到其最终调用的是native层实现的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 */
execute_dump(hprof_path.c_str());
/* analyze */
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);
}
}
其中fork_task,execute_dump和_exit在前文中我们均已介绍,这里重点看下execute_analyze的实现。
execute_analyze
execute_analyze实现代码如下所示:
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);
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 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);
// 分析DestroyedActivityInfo实例对象
return analyzer.Analyze([reference_key](const HprofHeap &heap) {
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;
for (const object_id_t leak_ref: heap.GetInstances(leak_ref_class_id)) {
const object_id_t key_string_id = unwrap_optional(
heap.GetFieldReference(leak_ref, "mKey"), continue);
const std::string &key_string = unwrap_optional(
heap.GetValueFromStringInstance(key_string_id), continue);
if (key_string != reference_key)
continue;
const object_id_t weak_ref = unwrap_optional(
heap.GetFieldReference(leak_ref, "mActivityRef"), continue);
const object_id_t leak = unwrap_optional(
heap.GetFieldReference(weak_ref, "referent"), continue);
leaks.emplace_back(leak);
}
return leaks;
});
}
解析hprof文件生成HprofHeap
css
// HprofAnalyzer的实现类,可以看到通过parser解析数据
HprofAnalyzerImpl::HprofAnalyzerImpl(void *data, size_t data_size) :
data_(data),
data_size_(data_size),
parser_(new internal::parser::HeapParser()),
exclude_matcher_group_() {}
// HeapParser的核心实现逻辑在HeapParserEngineImpl
HeapParser::HeapParser() :
engine_(new HeapParserEngineImpl()) {}
c
// HeapParserEngineImpl解析hprof文件头
void HeapParserEngineImpl::ParseHeader(reader::Reader &reader, heap::Heap &heap) const {
// Version string.
const std::string version = reader.ReadNullTerminatedString();
if (version != "JAVA PROFILE 1.0" &&
version != "JAVA PROFILE 1.0.1" &&
version != "JAVA PROFILE 1.0.2" &&
version != "JAVA PROFILE 1.0.3") {
pub_fatal("invalid HPROF header");
}
// Identifier size.
heap.InitializeIdSize(reader.ReadU4());
// Skip timestamp.
reader.SkipU8();
}
arduino
// 根据不同的tag解析Record内容
void
HeapParserEngineImpl::Parse(reader::Reader &reader, heap::Heap &heap,
const ExcludeMatcherGroup &exclude_matcher_group,
const HeapParserEngine &next) const {
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:
next.ParseStringRecord(reader, heap, length);
break;
case tag::kLoadClasses:
next.ParseLoadClassRecord(reader, heap, length);
break;
case tag::kHeapDump:
case tag::kHeapDumpSegment:
next.ParseHeapContent(reader, heap, length, exclude_matcher_group, next);
break;
case tag::kHeapDumpEnd:
goto break_read_loop;
default:
reader.Skip(length);
break;
}
}
break_read_loop:
next.LazyParse(heap, exclude_matcher_group);
}
查找Gc Root path
arduino
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) {
std::map<heap::object_id_t, ref_node_t> traversed;
std::deque<ref_node_t> waiting;
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;
for (const auto &[referent, reference]: heap.GetLeakReferenceGraph().at(referrer_id)) {
try {
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{
.referrer_id = referrer_id,
.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) {
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;
}
}
std::reverse(ret[leak].begin(), ret[leak].end());
}
}
return std::move(ret);
}