1、前言
KOOM 的内存监控,分为三大块,分别为 Java 、Native 和 Thread,此篇主要是对 Thread 的内存监控模块的探索。
- KOOM 源码解读 - java 监控
- KOOM 源码解读 - native 监控
- KOOM 源码解读 - thread 监控
2、前置知识
对于监控应用的线程泄露,它的核心原理是:hook pthread_create/pthread_detach/pthread_join/pthread_exit 等线程方法,用于记录线程的生命周期和创建线程时的堆栈、名称等信息;
当发现一个 joinable 的线程,在没有 detach 或者 join 的情况下,执行了 pthread_exit,则视为线程泄露,记录该线程信息;
为什么一个 joinable 的线程,在没有 detach 或者 join 的情况下执行 pthread_exit 会出现泄露?
因为当通过 pthread_create 创建线程后,如果线程执行完后,执行 pthread_exit 退出线程。这样虽然结束了当前线程,但是线程的资源是不会被回收的。这是由于线程是共享进程的资源,如果线程退出了,而之前没有执行 join 或者是 执行 detach 分离线程,就不会回收该线程的资源。
3、源码分析
我们从 demo 中的 ThreadLeakTestActivity.kt 开始分析。
3.1 初始化
调用 ThreadMonitor.startTrackAsync() 方法开始初始化
fun startTrack() {
// 初始化成功
if (handleNativeInit()) {
mIsRunning = true
// 启动 loop
startLoop(clearQueue = true, postAtFront = false, delayMillis = monitorConfig.startDelay)
}
}
fun startTrackAsync() {
getLoopHandler().postAtFrontOfQueue {
startTrack()
}
}
private fun handleNativeInit(): Boolean {
// 省略了一些配置信息
// 3.1.1 经过调用,最后调用到 koom.cpp 的 Start()
NativeHandler.start()
return true
}
3.1.1 koom.cpp 的 Start()
void Start() {
if (isRunning) {
return;
}
// 初始化数据
delete sHookLooper;
// 3.1.2 新建消息机制
sHookLooper = new HookLooper();
// 3.1.4 开启 hook 线程方法
koom::ThreadHooker::Start();
isRunning = true;
}
3.1.2 新建消息机制
void HookLooper::handle(int what, void *data) {
looper::handle(what, data);
switch (what) {
case ACTION_ADD_THREAD: { // 新增线程
koom::Log::info(looper_tag, "AddThread");
auto info = static_cast<HookAddInfo *>(data);
holder->AddThread(info->tid, info->pthread, info->is_thread_detached,
info->time, info->create_arg);
delete info;
break;
}
case ACTION_JOIN_THREAD: { // join 线程
koom::Log::info(looper_tag, "JoinThread");
auto info = static_cast<HookInfo *>(data);
holder->JoinThread(info->thread_id);
delete info;
break;
}
case ACTION_DETACH_THREAD: { // detech 线程
koom::Log::info(looper_tag, "DetachThread");
auto info = static_cast<HookInfo *>(data);
holder->DetachThread(info->thread_id);
delete info;
break;
}
case ACTION_EXIT_THREAD: { // exit 线程
koom::Log::info(looper_tag, "ExitThread");
auto info = static_cast<HookExitInfo *>(data);
holder->ExitThread(info->thread_id, info->threadName, info->time);
delete info;
break;
}
case ACTION_REFRESH: { // 生成线程泄露报告
koom::Log::info(looper_tag, "Refresh");
auto info = static_cast<SimpleHookInfo *>(data);
holder->ReportThreadLeak(info->time);
delete info;
break;
}
default: {
}
}
}
这里最终的处理,都在 thread_holder.cpp 中
3.1.3 thread_holder.cpp 中的处理
std::map<pthread_t, ThreadItem> leakThreadMap;
std::map<pthread_t, ThreadItem> threadMap;
// ACTION_ADD_THREAD
void ThreadHolder::AddThread(int tid, pthread_t threadId, bool isThreadDetached,
int64_t start_time, ThreadCreateArg *create_arg) {
bool valid = threadMap.count(threadId) > 0;
if (valid) return;
// 保存到 threadMap
auto &item = threadMap[threadId];
// 这里省略了一些线程数据的处理和线程堆栈信息的处理
}
// ACTION_JOIN_THREAD
void ThreadHolder::JoinThread(pthread_t threadId) {
bool valid = threadMap.count(threadId) > 0;
if (valid) {
// 线程资源会释放
threadMap[threadId].thread_detached = true;
} else {
// 从 leakThreadMap 中移除
leakThreadMap.erase(threadId);
}
}
// ACTION_EXIT_THREAD
void ThreadHolder::ExitThread(pthread_t threadId, std::string &threadName,
long long int time) {
bool valid = threadMap.count(threadId) > 0;
if (!valid) return;
auto &item = threadMap[threadId];
item.exitTime = time;
item.name.assign(threadName);
// 退出线程时,是否有释放资源
if (!item.thread_detached) {
// 泄露了
leakThreadMap[threadId] = item;
}
threadMap.erase(threadId);
}
// ACTION_DETACH_THREAD
void ThreadHolder::DetachThread(pthread_t threadId) {
bool valid = threadMap.count(threadId) > 0;
if (valid) {
// 线程资源会释放
threadMap[threadId].thread_detached = true;
} else {
// 从 leakThreadMap 中移除
leakThreadMap.erase(threadId);
}
}
// ACTION_REFRESH
void ThreadHolder::ReportThreadLeak(long long time) {
int needReport{};
// ...
// 遍历打印线程泄露的信息
for (auto &item : leakThreadMap) {
if (item.second.exitTime + delay < time && !item.second.thread_reported) {
WriteThreadJson(writer, item.second);
}
}
// ...
}
3.1.4 开启 hook 线程方法
void ThreadHooker::Start() { ThreadHooker::InitHook(); }
void ThreadHooker::InitHook() {
koom::Log::info(thread_tag, "HookSo init hook");
std::set<std::string> libs;
DlopenCb::GetInstance().GetLoadedLibs(libs);
HookLibs(libs, Constant::kDlopenSourceInit);
DlopenCb::GetInstance().AddCallback(DlopenCallback);
}
void ThreadHooker::HookLibs(std::set<std::string> &libs, int source) {
for (const auto &lib : libs) {
hooked |= ThreadHooker::RegisterSo(lib, source);
}
}
bool ThreadHooker::RegisterSo(const std::string &lib, int source) {
if (IsLibIgnored(lib)) {
return false;
}
auto lib_ctr = lib.c_str();
koom::Log::info(thread_tag, "HookSo %d %s", source, lib_ctr);
// 3.1.5 HookThreadCreate
xhook_register(lib_ctr, "pthread_create",
reinterpret_cast<void *>(HookThreadCreate), nullptr);
// 3.1.6 HookThreadDetach
xhook_register(lib_ctr, "pthread_detach",
reinterpret_cast<void *>(HookThreadDetach), nullptr);
// 3.1.7 HookThreadJoin
xhook_register(lib_ctr, "pthread_join",
reinterpret_cast<void *>(HookThreadJoin), nullptr);
// 3.1.8 HookThreadExit
xhook_register(lib_ctr, "pthread_exit",
reinterpret_cast<void *>(HookThreadExit), nullptr);
return true;
}
这里分别 hook 了 pthread_create/pthread_detach/pthread_join/pthread_exit 这四个线程方法。
3.1.5 HookThreadCreate
这里最终调用到 thread_hokder.cpp 中的 AddThread 函数。
int ThreadHooker::HookThreadCreate(pthread_t *tidp, const pthread_attr_t *attr,
void *(*start_rtn)(void *), void *arg) {
if (hookEnabled() && start_rtn != nullptr) {
auto time = Util::CurrentTimeNs();
koom::Log::info(thread_tag, "HookThreadCreate");
auto *hook_arg = new StartRtnArg(arg, Util::CurrentTimeNs(), start_rtn);
auto *thread_create_arg = hook_arg->thread_create_arg;
void *thread = koom::CallStack::GetCurrentThread();
if (thread != nullptr) {
koom::CallStack::JavaStackTrace(thread,
hook_arg->thread_create_arg->java_stack);
}
koom::CallStack::FastUnwind(thread_create_arg->pc,
koom::Constant::kMaxCallStackDepth);
thread_create_arg->stack_time = Util::CurrentTimeNs() - time;
return pthread_create(tidp, attr,
reinterpret_cast<void *(*)(void *)>(HookThreadStart),
reinterpret_cast<void *>(hook_arg));
}
return pthread_create(tidp, attr, start_rtn, arg);
}
ALWAYS_INLINE void ThreadHooker::HookThreadStart(void *arg) {
...
auto info = new HookAddInfo(tid, Util::CurrentTimeNs(), self,
state == PTHREAD_CREATE_DETACHED,
hookArg->thread_create_arg);
// 调用到 hook_looper.cpp , 处理 ACTION_ADD_THREAD
sHookLooper->post(ACTION_ADD_THREAD, info);
...
}
3.1.6 HookThreadDetach
这里最终调用到 thread_hokder.cpp 中的 DetachThread 函数。
int ThreadHooker::HookThreadDetach(pthread_t t) {
if (!hookEnabled()) return pthread_detach(t);
int c_tid = (int)syscall(SYS_gettid);
koom::Log::info(thread_tag, "HookThreadDetach c_tid:%0x", c_tid);
auto info = new HookInfo(t, Util::CurrentTimeNs());
sHookLooper->post(ACTION_DETACH_THREAD, info);
return pthread_detach(t);
}
3.1.7 HookThreadJoin
这里最终调用到 thread_hokder.cpp 中的 JoinThread 函数。
int ThreadHooker::HookThreadJoin(pthread_t t, void **return_value) {
if (!hookEnabled()) return pthread_join(t, return_value);
int c_tid = (int)syscall(SYS_gettid);
koom::Log::info(thread_tag, "HookThreadJoin c_tid:%0x", c_tid);
auto info = new HookInfo(t, Util::CurrentTimeNs());
sHookLooper->post(ACTION_JOIN_THREAD, info);
return pthread_join(t, return_value);
}
3.1.8 HookThreadExit
这里最终调用到 thread_hokder.cpp 中的 ExitThread 函数。
void ThreadHooker::HookThreadExit(void *return_value) {
if (!hookEnabled()) pthread_exit(return_value);
koom::Log::info(thread_tag, "HookThreadExit");
int tid = (int)syscall(SYS_gettid);
char thread_name[16]{};
prctl(PR_GET_NAME, thread_name);
auto info =
new HookExitInfo(pthread_self(), tid, thread_name, Util::CurrentTimeNs());
sHookLooper->post(ACTION_EXIT_THREAD, info);
pthread_exit(return_value);
}
3.1.9 小结
- 初始化配置信息
- 初始化 native loop 消息机制
- hook 线程方法
- 调用 pthread_create 时,把线程相关信息 ThreadItem 保存到 threadMap
- 调用 pthread_detach 或 pthread_join 时,把该 ThreadItem.thread_detached 复制为true
- 当退出线程时,即调用 pthread_exit ,判断 ThreadItem.thread_detached 是否为true,不为 true 则表示该线程资源未被回收,视为线程泄露,该 ThreadItem 保存到 leakThreadMap
3.2 monitor 检测
在 ThreadMonitor 中的 handleNativeInit() 初始化成功后,会调用 startLoop 方法启动 monitor 检测,会不断回调 call() 函数。
// ThreadMonitor
override fun call(): LoopState {
handleThreadLeak()
return LoopState.Continue
}
private fun handleThreadLeak() {
NativeHandler.refresh()
}
// jni_bridge.cpp
JNIEXPORT void JNICALL
Java_com_kwai_performance_overhead_thread_monitor_NativeHandler_refresh(
JNIEnv *env, jclass obj) {
koom::Refresh();
}
// koom.cpp
void Refresh() {
auto info = new SimpleHookInfo(Util::CurrentTimeNs());
sHookLooper->post(ACTION_REFRESH, info);
}
// hook_looper.cpp
case ACTION_REFRESH: {
koom::Log::info(looper_tag, "Refresh");
auto info = static_cast<SimpleHookInfo *>(data);
// 3.2.1 最终调用 thread_hokder.cpp 中的 ReportThreadLeak() 函数
holder->ReportThreadLeak(info->time);
delete info;
break;
}
3.2.1 thread_hokder.cpp 中的 ReportThreadLeak() 函数
void ThreadHolder::ReportThreadLeak(long long time) {
int needReport{};
const char *type = "detach_leak";
auto delay = threadLeakDelay * 1000000LL; // ms -> ns
rapidjson::StringBuffer jsonBuf;
rapidjson::Writer<rapidjson::StringBuffer> writer(jsonBuf);
writer.StartObject();
writer.Key("leakType");
writer.String(type);
writer.Key("threads");
writer.StartArray();
for (auto &item : leakThreadMap) {
if (item.second.exitTime + delay < time && !item.second.thread_reported) {
koom::Log::info(holder_tag, "ReportThreadLeak %ld, %ld, %ld",
item.second.exitTime, time, delay);
needReport++;
item.second.thread_reported = true;
WriteThreadJson(writer, item.second);
}
}
writer.EndArray();
writer.EndObject();
koom::Log::info(holder_tag, "ReportThreadLeak %d", needReport);
if (needReport) {
// 3.2.2 通过 jni 调用 java 层方法,把泄露的线程信息返回给上层处理
JavaCallback(jsonBuf.GetString());
// clean up
auto it = leakThreadMap.begin();
for (; it != leakThreadMap.end();) {
if (it->second.thread_reported) {
leakThreadMap.erase(it++);
} else {
it++;
}
}
}
}
这里主要把 leakThreadMap 中的各个线程的信息整理成一个 jsonBuf,然后返回给上层处理。
3.2.2 通过 jni 调用 java 层方法,把泄露的线程信息返回给上层处理
void Init(JavaVM *vm, _JNIEnv *env) {
java_vm_ = vm;
auto clazz = env->FindClass(
"com/kwai/performance/overhead/thread/monitor/NativeHandler");
native_handler_class = static_cast<jclass>(env->NewGlobalRef(clazz));
java_callback_method = env->GetStaticMethodID(
native_handler_class, "nativeReport", "(Ljava/lang/String;)V");
}
void JavaCallback(const char *value, bool doAttach) {
JNIEnv *env = GetEnv(doAttach);
if (env != nullptr && value != nullptr) {
Log::error("koom", "JavaCallback %d", strlen(value));
jstring string_value = env->NewStringUTF(value);
// 3.2.3 调用 NativeHandler.kt 的 nativeReport 函数
env->CallStaticVoidMethod(native_handler_class, java_callback_method,
string_value);
Log::info("koom", "JavaCallback finished");
} else {
Log::info("koom", "JavaCallback fail no JNIEnv");
}
}
3.2.3 调用 NativeHandler.kt 的 nativeReport 函数
@JvmStatic
fun nativeReport(resultJson: String) {
// 3.2.4 最后调用到 ThreadMonitor 的 nativeReport 函数
ThreadMonitor.nativeReport(resultJson)
}
3.2.4 ThreadMonitor 的 nativeReport 函数
fun nativeReport(resultJson: String) {
// 把 json 转成 ThreadLeakContainer,然后返回给 ThreadLeakListener
mGon.fromJson(resultJson, ThreadLeakContainer::class.java).let {
monitorConfig.listener?.onReport(it.threads)
}
}
最终会把结果返回给 初始化配置信息时添加的 ThreadLeakListener。