当前位置: 首页 > news >正文

【Android 内存优化】KOOM线程泄漏监控的实现源码分析

文章目录

  • 线程monitor的流程
  • 怎么判断线程是否泄漏
    • AddThread
    • JoinThread
    • ExitThread
    • DetachThread
  • 总结

前面我们通过研究KOOM的开源代码,研究了关于Java层和native层内存泄漏监控的实现原理。还剩下线程泄漏这部分没有进行分析,今天来补全它。整体下来,相信我们对于内存监控在代码上的实现上会有一个较为体系化的了解。

线程monitor的流程

从开启monitor的startLoop方法开始:

override fun call(): LoopState {handleThreadLeak()return LoopState.Continue
}

一路进入方法栈,到了这里:

void Refresh() {auto info = new SimpleHookInfo(Util::CurrentTimeNs());sHookLooper->post(ACTION_REFRESH, info);
}

看下sHookLooper类:

namespace koom {
class HookLooper : public looper {public:koom::ThreadHolder *holder;HookLooper();~HookLooper();void handle(int what, void *data);void post(int what, void *data);
};

根据ACTION_REFRESH来看看有哪些action:

enum HookAction {ACTION_ADD_THREAD,ACTION_START_THREAD,ACTION_JOIN_THREAD,ACTION_EXIT_THREAD,ACTION_DETACH_THREAD,ACTION_INIT,ACTION_REFRESH,ACTION_SET_NAME,
};

根据action的处理,找到了handler处理message的地方:

namespace koom {
const char *looper_tag = "koom-hook-looper";
HookLooper::HookLooper() : looper() { this->holder = new koom::ThreadHolder(); }
HookLooper::~HookLooper() { delete this->holder; }
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: {koom::Log::info(looper_tag, "JoinThread");auto info = static_cast<HookInfo *>(data);holder->JoinThread(info->thread_id);delete info;break;}case ACTION_DETACH_THREAD: {koom::Log::info(looper_tag, "DetachThread");auto info = static_cast<HookInfo *>(data);holder->DetachThread(info->thread_id);delete info;break;}case ACTION_EXIT_THREAD: {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: {}}
}
void HookLooper::post(int what, void *data) { looper::post(what, data); }
}  // namespace koom

可以发现不同线程相关的操作都进行了处理。

以HookThreadStart为例,看看发送这个message的地方:

ALWAYS_INLINE void ThreadHooker::HookThreadStart(void *arg) {koom::Log::info(thread_tag, "HookThreadStart");auto *hookArg = (StartRtnArg *)arg;pthread_attr_t attr;pthread_t self = pthread_self();int state = 0;if (pthread_getattr_np(self, &attr) == 0) {pthread_attr_getdetachstate(&attr, &state);}int tid = (int)syscall(SYS_gettid);koom::Log::info(thread_tag, "HookThreadStart %p, %d, %d", self, tid,hookArg->thread_create_arg->stack_time);auto info = new HookAddInfo(tid, Util::CurrentTimeNs(), self,state == PTHREAD_CREATE_DETACHED,hookArg->thread_create_arg);sHookLooper->post(ACTION_ADD_THREAD, info);void *(*start_rtn)(void *) = hookArg->start_rtn;void *routine_arg = hookArg->arg;delete hookArg;start_rtn(routine_arg);
}

这个方法被HookThreadCreate方法调用:

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);
}

HookThreadCreate又被RegisterSo调用。

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);xhook_register(lib_ctr, "pthread_create",reinterpret_cast<void *>(HookThreadCreate), nullptr);xhook_register(lib_ctr, "pthread_detach",reinterpret_cast<void *>(HookThreadDetach), nullptr);xhook_register(lib_ctr, "pthread_join",reinterpret_cast<void *>(HookThreadJoin), nullptr);xhook_register(lib_ctr, "pthread_exit",reinterpret_cast<void *>(HookThreadExit), nullptr);return true;
}

来到这里,hook实现的地方找到了,还是通过爱奇艺的xhook,把线程操作的系统API给hook出来了。

到这里,整体的实现思路出来了,通过looper不断轮询获取handler定时发送的message去refresh一些进程里面各个线程相关信息。

而线程信息则是通过native hook技术中中PLT hook来实现hook和信息获取。

上述分析,知道了整体实现hook的流程,但是拿到系统API之后,做了什么。下面继续分析:

怎么判断线程是否泄漏

AddThread


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;koom::Log::info(holder_tag, "AddThread tid:%d pthread_t:%p", tid, threadId);auto &item = threadMap[threadId];item.Clear();item.thread_internal_id = threadId;item.thread_detached = isThreadDetached;item.startTime = start_time;item.create_time = create_arg->time;item.id = tid;std::string &stack = item.create_call_stack;stack.assign("");try {// native stackint ignoreLines = 0;for (int index = 0; index < koom::Constant::kMaxCallStackDepth; ++index) {uintptr_t p = create_arg->pc[index];if (p == 0) continue;// koom::Log::info(holder_tag, "unwind native callstack #%d pc%p", index,// p);std::string line = koom::CallStack::SymbolizePc(p, index - ignoreLines);if (line.empty()) {ignoreLines++;} else {line.append("\n");stack.append(line);}}// java stackstd::vector<std::string> splits =koom::Util::Split(create_arg->java_stack.str(), '\n');for (const auto &split : splits) {if (split.empty()) continue;std::string line;line.append("#");line.append(split);line.append("\n");stack.append(line);}//空白堆栈,去掉##if (stack.size() == 3) stack.assign("");} catch (const std::bad_alloc &) {stack.assign("error:bad_alloc");}delete create_arg;koom::Log::info(holder_tag, "AddThread finish");
}

这里拿到了线程创建时间和id等信息。

JoinThread

void ThreadHolder::JoinThread(pthread_t threadId) {bool valid = threadMap.count(threadId) > 0;koom::Log::info(holder_tag, "JoinThread tid:%p", threadId);if (valid) {threadMap[threadId].thread_detached = true;} else {leakThreadMap.erase(threadId);}
}

ExitThread

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];koom::Log::info(holder_tag, "ExitThread tid:%p name:%s", threadId,item.name.c_str());item.exitTime = time;item.name.assign(threadName);if (!item.thread_detached) {// 泄露了koom::Log::error(holder_tag,"Exited thread Leak! Not joined or detached!\n tid:%p",threadId);leakThreadMap[threadId] = item;}threadMap.erase(threadId);koom::Log::info(holder_tag, "ExitThread finish");
}

DetachThread


void ThreadHolder::DetachThread(pthread_t threadId) {bool valid = threadMap.count(threadId) > 0;koom::Log::info(holder_tag, "DetachThread tid:%p", threadId);if (valid) {threadMap[threadId].thread_detached = true;} else {leakThreadMap.erase(threadId);}
}

可以发现,代码是通过thread_detached这个参数来判断线程是否泄漏了,假如进程执行了ExitThread,但是thread_detached还没有解除,则判断为线程泄漏。

接着就是收集一些线程的信息,存储到容器里面。

这些为后续做堆栈回溯信息和整体信息的记录做了一些准备。

总结

通过通篇下来,关于线程泄漏监控相关的思路我们是有了,但是具体细节其实还有很多。
篇幅和时间原因,这篇就介绍到这里。

相关文章:

【Android 内存优化】KOOM线程泄漏监控的实现源码分析

文章目录 线程monitor的流程怎么判断线程是否泄漏AddThreadJoinThreadExitThreadDetachThread 总结 前面我们通过研究KOOM的开源代码&#xff0c;研究了关于Java层和native层内存泄漏监控的实现原理。还剩下线程泄漏这部分没有进行分析&#xff0c;今天来补全它。整体下来&…...

【爬虫基础】第1讲 网络爬虫基本知识

什么是网络爬虫 网络爬虫&#xff08;Web crawler&#xff09;是一种自动化程序&#xff0c;用于在互联网上收集信息。它可以通过扫描和解析网页的超链接&#xff0c;自动访问网页并抓取所需的数据。网络爬虫常用于搜索引擎和数据采集工具中。 作用 通过有效的爬虫手段批量采…...

scrapy爬虫框架

scrapy爬虫框架 一、scrapy的概念作用和工作流程1、scrapy的概念2、scrapy框架的作用3、scrapy的工作流程&#xff08;重点&#xff09;3.1 回顾之前的爬虫流程3.2 改写上述流程3.3 scrapy的流程3.4 scrapy的三个内置对象3.5 scrapy中每个模块的具体作用 二、scrapy的入门使用1…...

【深度学习】基础知识

吴恩达DeepLearning Python # 1.numpy c c.ravel() 将多维数组拉平 # 2.time tic time.time() toc time.time() print(str(1000*(toc- tic))"ms")...

Electron应用自动更新实现及打包部署全攻略

Electron应用自动更新实现及打包部署全攻略 Electron自动更新原理配置更新服务器打包与发布更新全攻略实战步骤部署与测试部署更新测试更新流程错误处理与调试 高级特性与优化用户体验与反馈安全与隐私保护维护与持续集成性能优化结语 在现代跨平台桌面应用开发领域中&#xff…...

【爬虫基础】第6讲 opener的使用

在爬虫中&#xff0c;opener是一个用来发送HTTP请求的对象。它可以用来模拟浏览器发送请求&#xff0c;包括设置请求头、处理Cookie等操作。使用opener可以实现一些高级功能&#xff0c;如模拟登录、处理验证码等。 方法1&#xff1a; from urllib.request import Request,bu…...

Milvus 向量数据库:如何基于docker-compose在本地快速搭建测试环境

文章目录 1. 安装 milvus1.1. milvus v2.3.12版本信息1.2. 安装milvus步骤1.3. 安装管理工具Attu1.4. 将Attu由docker-compose启动参考Milvus 向量数据库专为向量查询与检索设计,能够为万亿级向量数据建立索引,详见介绍请参见: milvus: 专为向量查询与检索设计的向量数据库 …...

python --dejavu音频指纹识别

Dejavu是一个用于音频指纹识别的Python库&#xff0c;它能够将音频文件转换成独特的指纹&#xff0c;然后通过比对数据库中已知音频的指纹&#xff0c;识别出输入音频的来源或相似音频。Dejavu库支持多种类型的音频文件&#xff0c;包括MP3、WAV等&#xff0c;同时也支持在不同…...

完全二叉树的层序遍历[天梯赛]

文章目录 题目描述思路 题目描述 输入样例 8 91 71 2 34 10 15 55 18 输出样例 18 34 55 71 2 10 15 91思路 完全二叉树最后一层可以不满&#xff0c;但上面的每一层的节点数都是满的 后序遍历的顺序为"左右根"&#xff0c;我们可以用数组模拟完全二叉树&#xff0c;…...

C语言看完我这篇编译与链接就够啦!!!

1. 前言 Hello&#xff01;大家好我是小陈&#xff0c;今天来给大家介绍最详细的C语言编译与链接。 2. 编译和链接 我们通常用的编译器&#xff0c;比如Visual Sudio,这样的IDE(集成开发环境&#xff09;一般将编译和链接的过程一步完成&#xff0c;通常将这这种编译和链接合…...

【React】react 使用 lazy 懒加载模式的组件写法,外面需要套一层 Loading 的提示加载组件

react 组件按需加载问题解决 1 错误信息2 解决方案 1 错误信息 react 项目在创建 router 路由时&#xff0c;使用 lazy 懒加载时&#xff0c;导致以下报错&#xff1a; The above error occurred in the <Route.Provider> component:Uncaught Error: A component suspe…...

IDEA的Scala环境搭建

目录 前言 Scala的概述 Scala环境的搭建 一、配置Windows的JAVA环境 二、配置Windows的Scala环境 编写一个Scala程序 前言 学习Scala最好先掌握Java基础及高级部分知识&#xff0c;文章正文中会提到Scala与Java的联系&#xff0c;简单来讲Scala好比是Java的加强版&#x…...

LeetCode第四天(448. 找到所有数组中消失的数字)

给你一个含 n 个整数的数组 nums &#xff0c;其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字&#xff0c;并以数组的形式返回结果。 官解&#xff1a; 方法一&#xff1a;原地修改 思路及解法 我们可以用一个哈希表记录数组 nums 中…...

【vivado】在原有工程上新建工程

一、前言 在工作中&#xff0c;我们经常需要接触到别人的工程&#xff0c;并在别人的工程上新加设计功能&#xff0c;此时我们需要以别人工程为基础新建工程。 二、在已有工程上新建工程的方法 2.1 vivado 页面file-project-save as... 该方法的优点为&#xff1a;可以直接…...

(原型与原型链)前端八股文修炼Day5

一 原型链的理解 原型链定义&#xff1a; 原型链是 JavaScript 中实现对象继承的关键机制之一&#xff0c;它是一种对象之间的关系&#xff0c;通过这种关系&#xff0c;一个对象可以继承另一个对象的属性和方法。 原型链的组成&#xff1a; 每个对象都有一个指向另一个对象的…...

逐步学习Go-并发通道chan(channel)

概述 Go的Routines并发模型是基于CSP&#xff0c;如果你看过七周七并发&#xff0c;那么你应该了解。 什么是CSP&#xff1f; "Communicating Sequential Processes"&#xff08;CSP&#xff09;这个词组的含义来自其英文直译以及在计算机科学中的使用环境。 CSP…...

鸿蒙HarmonyOS应用开发之Node-API开发规范

当传入napi_get_cb_info的argv不为nullptr时&#xff0c;argv的长度必须大于等于传入argc声明的大小。 当argv不为nullptr时&#xff0c;napi_get_cb_info会根据argc声明的数量将JS实际传入的参数写入argv。如果argc小于等于实际JS传入参数的数量&#xff0c;该接口仅会将声明…...

单例模式

文章目录 单例模式特殊类的设计单例模式饿汉模式懒汉模式懒汉VS饿汉懒汉的线程安全单例对象的释放 单例模式 认识单例模式之前先认识一下几种常见的特殊类的设计。 特殊类的设计 设计一个类 只能再堆上创建对象 只能再堆上创建&#xff0c;则通过new来创建对象。 将类的构造函…...

Android OpenMAX - 开篇

Android Media是一块非常庞大的内容&#xff0c;上到APP的书写&#xff0c;中到播放器的实现、封装格式的了解&#xff0c;下到OMX IL层的实现、Decoder的封装&#xff0c;每一块都需要我们下很大的功夫学习。除此之外&#xff0c;我们还要对一些相关的模块进行了解&#xff0c…...

ubuntu开启ssh服务

1.安装openssh-server sudo apt-get install openssh-server 2.开启服务 sudo servive ssh start 3.设置开机自启动 sudo systemctl enable ssh 4.检查是否开启成功 ps -ef | grep ssh 如使用xshell等连接时失败&#xff0c;可以尝试关闭防火墙&#xff1a; sudo sys…...

第19节 Node.js Express 框架

Express 是一个为Node.js设计的web开发框架&#xff0c;它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用&#xff0c;和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

Frozen-Flask :将 Flask 应用“冻结”为静态文件

Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是&#xff1a;将一个 Flask Web 应用生成成纯静态 HTML 文件&#xff0c;从而可以部署到静态网站托管服务上&#xff0c;如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

2021-03-15 iview一些问题

1.iview 在使用tree组件时&#xff0c;发现没有set类的方法&#xff0c;只有get&#xff0c;那么要改变tree值&#xff0c;只能遍历treeData&#xff0c;递归修改treeData的checked&#xff0c;发现无法更改&#xff0c;原因在于check模式下&#xff0c;子元素的勾选状态跟父节…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...

vulnyx Blogger writeup

信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面&#xff0c;gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress&#xff0c;说明目标所使用的cms是wordpress&#xff0c;访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...

Golang——6、指针和结构体

指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...

打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用

一、方案背景​ 在现代生产与生活场景中&#xff0c;如工厂高危作业区、医院手术室、公共场景等&#xff0c;人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式&#xff0c;存在效率低、覆盖面不足、判断主观性强等问题&#xff0c;难以满足对人员打手机行为精…...

淘宝扭蛋机小程序系统开发:打造互动性强的购物平台

淘宝扭蛋机小程序系统的开发&#xff0c;旨在打造一个互动性强的购物平台&#xff0c;让用户在购物的同时&#xff0c;能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机&#xff0c;实现旋转、抽拉等动作&#xff0c;增…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...

pycharm 设置环境出错

pycharm 设置环境出错 pycharm 新建项目&#xff0c;设置虚拟环境&#xff0c;出错 pycharm 出错 Cannot open Local Failed to start [powershell.exe, -NoExit, -ExecutionPolicy, Bypass, -File, C:\Program Files\JetBrains\PyCharm 2024.1.3\plugins\terminal\shell-int…...