KOOM 源码解读 - thread 监控

3,832 阅读5分钟

1、前言

KOOM 的内存监控,分为三大块,分别为 JavaNativeThread,此篇主要是对 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 小结

  1. 初始化配置信息
  2. 初始化 native loop 消息机制
  3. hook 线程方法
  4. 调用 pthread_create 时,把线程相关信息 ThreadItem 保存到 threadMap
  5. 调用 pthread_detach 或 pthread_join 时,把该 ThreadItem.thread_detached 复制为true
  6. 当退出线程时,即调用 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。

4、参考

  1. https://github.com/KwaiAppTeam/KOOM