android 如何分析应用的内存(十)——malloc统计和libmemunreachable

news2024/7/6 17:52:55

android 如何分析应用的内存(十)

接下来介绍native heap内存的第四个板块————malloc统计和libmemunreachable

malloc统计

malloc统计是标准c库提供的接口。他有两个调用接口如下:

#include <malloc.h>

struct mallinfo mallinfo(void);
struct mallinfo2 mallinfo2(void);

他们返回内存分配相关的统计信息。其中mallinfo2结构体和mallinfo结构体字段一样,区别仅是:mallinfo结构体的字段类型为int,而mallinfo2结构体的字段类型为size_t.

int类型会导致某些情况下长度不够。因此应该使用mallinfo2函数。

需要注意的是,mallinfo2和mallinfo统计的是malloc函数,或者相关函数的内存分配情况。
对于其他非标方法获得的内存,无法通过这两个函数统计到。此时可以使用如下的函数

// 将所有的内存统计,以xml格式,输出到stream中
int malloc_info(int options, FILE *stream);

mallinfo2结构体解释:

struct mallinfo2 {
    size_t arena;     /* 总的分配字节数(不包含映射区域),包含空闲和非空闲 */
    size_t ordblks;   /* 空闲块数 */
    size_t smblks;    /* fastbin块数(这是一种小内存块) */
    size_t hblks;     /* mmap分配的块数 */
    size_t hblkhd;    /* mmap分配的字节数 */
    size_t usmblks;   /* 未被使用,总是为0 */
    size_t fsmblks;   /* fastbin 空闲块中的字节总数 */
    size_t uordblks;  /* 正在使用的分配的字节总数 */
    size_t fordblks;  /* 空闲块的字节数 */
    size_t keepcost;  /* 堆顶可释放空间的总量。理想情况下(即忽略页面对齐限制等),
    					这是通过 malloc_trim 可以释放的字节的最大数量。 */
};

注意:应用程序使用c库的函数,请求内存分配,返回的是由c库维护的内存池。因此内存池就会被分成空闲和非空闲两种。

注意:fastbin块,可以叫做“快速块”。它是c库维护的一种特殊内存块,它比较小。当调用malloc分配小块内存时,就从fastbin中分配内存,而不是从常规内存池中分配。这样有助于减少内存碎片

上面结构体注释中提及的malloc_trim函数,可以将c库维护的内存的顶端部分,返还给操作系统,以缓解内存紧张。而keepcost就是能够返回的最大值。

mallinfo2例子

先使用mallocinfo2函数,获取当前的内存统计信息。然后分配一段内存。再次使用mallocinfo2统计信息.

先定义一个函数printMallocInfo。该函数获取当前的内存统计信息,并打印:

void printMallocInfo(){
    struct mallinfo2 info = mallinfo2();

    ALOGD("总的字节数(空闲和非空闲)%zu",info.arena);
    ALOGD("空闲块数 %zu",info.ordblks);
    ALOGD("fastbin块数 %zu",info.smblks);
    ALOGD("mmap块数 %zu",info.hblks);
    ALOGD("mmap字节数 %zu",info.hblkhd);
    ALOGD("fastbin空闲字节数 %zu",info.fsmblks);
    ALOGD("已经分配字节数 %zu",info.uordblks);
    ALOGD("空闲块字节数 %zu",info.fordblks);
    ALOGD("堆顶可释放总量 %zu",info.keepcost);
}

然后分配一段内存,并再次打印,如下:

printMallocInfo();
//为了防止使用到fastbin中的内存块,因此一次分配1024个字节    
for(int i=0;i<100;++i){
    //分配1024个字节
    unsigned char * testChar = new unsigned char[1024];
    for(int i=0;i<1024;++i){
        testChar[i] = '0xa';
    }
    testChar[1023] = '\0';
    ALOGD("testChar %s",testChar);
}
printMallocInfo();

在Android的log系统重,检查输出值,如下:
在这里插入图片描述

从图中可以看到,分配前后,“已经分配字节数”相差102400.正是我们分配的大小。
但是在上面的截图中,有很多项一直是0,这是因为Android的libc库,目前只统计了
“已经分配的字节数”和“空闲块数”。那么其他的内存情况怎么查看呢?可以通过malloc_info进行查看

malloc_info例子

先定义malloc_info的调用函数如下:

void printMallocInfoToFile(){
    FILE * file = fopen("/sdcard/memroy.xml", "w");
    if(malloc_info(0,file) == 0 ) {
        ALOGD("malloc_info succeed");
    }else{
        ALOGD("malloc_info error %d ,%s",errno, strerror(errno));
    }
    fclose(file);
}

它会将相应的详细信息,输出到/sdcard/memroy.xml文件中。

注意:对于Android系统而言,要访问文件,需要使用对应的权限。

然后在合适的地方,调用上面的函数,最终,我们将看到如下的xml文件。

<malloc version="jemalloc-1">
    <heap nr="0">
        <allocated-large>3223552</allocated-large>
        <allocated-huge>4194304</allocated-huge>
        <allocated-bins>2996248</allocated-bins>
        <bin nr="0">
            <allocated>3992</allocated>
            <nmalloc>798</nmalloc>
            <ndalloc>299</ndalloc>
        </bin>
        <bin nr="1">
            <allocated>14816</allocated>
            <nmalloc>962</nmalloc>
            <ndalloc>36</ndalloc>
        </bin>
        <!--省略若干-->
        <bin nr="35">
            <allocated>200704</allocated>
            <nmalloc>26</nmalloc>
            <ndalloc>12</ndalloc>
        </bin>
        <bins-total>2996248</bins-total>
    </heap>
    <heap nr="1">
        <allocated-large>0</allocated-large>
        <allocated-huge>0</allocated-huge>
        <allocated-bins>269616</allocated-bins>
        <bin nr="0">
            <allocated>64</allocated>
            <nmalloc>8</nmalloc>
            <ndalloc>0</ndalloc>
        </bin>
        <bin nr="1">
            <allocated>176</allocated>
            <nmalloc>12</nmalloc>
            <ndalloc>1</ndalloc>
        </bin>
        <!--省略若干-->
        <bin nr="28">
            <allocated>40960</allocated>
            <nmalloc>10</nmalloc>
            <ndalloc>0</ndalloc>
        </bin>
        <bins-total>269616</bins-total>
    </heap>
</malloc>

下面是一个解释:

<malloc version="jemalloc-1"> :jemalloc分配器的版本
<heap nr="0">:第几号堆。为了保持线程的安全,每个线程有一个对应的堆。因此一个应用中可以
				有多个heap
<allocated-large>3223552</allocated-large>:已经分配的大内存字节数。如大数组,
											3D模型等
<allocated-huge>4194304</allocated-huge>:已经分配的巨大内存字节数。
<allocated-bins>2996248</allocated-bins>:已经分配的bins的字节数。bins是jemalloc
						使用的一种数据结构,这种数据结构,可以方便的管理heap中的内存。

事实上,查看heap的统计信息,只要前面三个就可以了。下面的bin是更加细致的描述。

<bin nr="0">
    <allocated>3992</allocated>
    <nmalloc>798</nmalloc>
    <ndalloc>299</ndalloc>
</bin>

nr:表示bin的编号
allocated:表示bin中已经分配的内存字节数
nmalloc和ndalloc:分别表示malloc和dalloc的调用次数。如果在一个运行周期内:
							dalloc次数远大于nmalloc,则可能存在内存泄漏

上面介绍的malloc统计信息,可以作用在时间轴上,可以观测heap的变化情况,而判断是否有内存泄漏。

一旦发现内存泄漏之后,可以使用libmemunreachable来查看具体的泄漏点。

libmemunreachable

libmemunreable是Android真正意义上的内存泄漏检测器。它在native层实现了类似于JVM的垃圾回收器,会去遍历所有的native heap内存,从而判断哪些内存不可达,即内存泄漏。

libmemunreachable 原理简述

现将整个流程简述如下:

  1. 被检测的某个线程,触发内存检测。(通过调用相应的函数,见后文)
  2. Android创建一个收集进程。这个收集进程和被检查的进程共享地址空间。
  3. 收集进程使用ptrace,控制被检查进程暂停所有的线程
  4. 收集进程,收集被检测进程的寄存器内容,堆栈内容,以及内存映射等数据
  5. 收集完成,然后创建,一个sweeper进程。该进程使用收集进程收集的各种数据,遍历内存
  6. 在遍历内存的时候,收集进程使用ptrace,再次运行被检测进程,然后收集进程退出
  7. 被检测进程继续运行,但触发内存泄漏的线程,停在原地,等待sweeper进程的分析结果。

如何启用libmemunreachable

第一步:为了能够打印更多的信息,需要按照malloc调试的方法,打开对应的开关,下面是在wrap.sh中的例子:

#!/system/bin/sh
export LIBC_DEBUG_MALLOC_OPTIONS=backtrace
exec "$@"

注意:一定要打开 useLegacyPackaging否则出现apk无法安装的情况

packagingOptions {
        jniLibs {
            useLegacyPackaging true
        }
    }

同样也可以直接在adb shell中操作如下:

adb root
adb shell setprop libc.debug.malloc.program app_process
adb shell setprop wrap.[process] "\$\@"
adb shell setprop libc.debug.malloc.options backtrace=16

注意:当打开malloc debug之后,会变得比较卡。为了能够跟踪特定的内存。可以使用
backtrace_size backtrace_min_size backtrace_max_size三个开关。他们可以限定跟踪的内存大小。详细说明见android 如何分析应用的内存(八)

第二步:有了上面的准备。在需要进行内存检测的地方,调用如下的函数。

//c接口
//将memory leak输出到log系统。limit表示最多有多少memory leak输出到log中。
//log_content表示memory leak前面是否有32字节的内容
// 如果调用成功,返回true
bool LogUnreachableMemory(bool log_contents, size_t limit);
//如果没有未达的内存,则返回true
bool NoLeaks();


//cpp接口
//暂时不做介绍,因为在我的环境中,没有跑通,可参阅:https://android.googlesource.com/platform/system/memory/libmemunreachable/+/master/README.md
bool GetUnreachableMemory(UnreachableMemoryInfo& info, size_t limit = 100);
std::string GetUnreachableMemoryString(bool log_contents = false, size_t limit = 100);

从如下地址中,获取对应的头文件:libmemunreachable

然后放入合适位置,如下:
在这里插入图片描述

第三步:将目标平台的相应的so库,pull出来,放入libs目录,如下:
在这里插入图片描述

然后添加相应的链接路径如下:

set(LINK_FLAGS    "-lmemunreachable -L/Users/biaowan/AndroidStudioProjects/Test_Malloc.old/app/libs/arm64-v8a")
set(CMAKE_SHARED_LINKER_FLAGS "${LINK_FLAGS}")

在这里插入图片描述

注意:对于应用开发者而言,一定要从对应平台中pull出相应的库。如,在Android 8.1上面运行测试,就pull Android 8.1的。
同时还要注意的是,图中有两个libc++.so和libc++_shared.so。他们存在相同的部分,因此当出现类似“can’t located symbolxxx”的错误时,可以相互更换名字试试。在我的例子中,libc++_shared.so和libc++.so均来自于ndk 25.2.9519653

注意:如果是Framework工程师,则无需这么繁琐,按照正常的编译流程即可

第四步:一旦运行成功,则会出现如下的内容
在这里插入图片描述

如上图,系统会将内存泄漏的详细堆栈打印出来。

至此,mallocinfo和libmemunreachable介绍完毕。

下一篇文章是heap分析的第五个板块:使用HWASan/Asan工具,查找内存错误

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/715586.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

敏捷开发Scrum

目录 1 Scrum概览1.2 理论基础1.2.1 透明性&#xff08;Transparency&#xff09;1.2.2 检验&#xff08;Inspection&#xff09;1.2.3 适应&#xff08;Adaptation&#xff09; 2 三个角色2.1 产品负责人&#xff08;Product Owner&#xff09;2.1.1 职责2.1.2 人选 2.2 流程管…

JDK多版本管理工具jenv

JENV mac jdk版本管理工具 Mac 安装jenv可以使用brew brew install jenv配置jenv zsh配置方式&#xff1a; echo export PATH"$HOME/.jenv/bin:$PATH" >> ~/.zshrc echo eval "$(jenv init -)" >> ~/.zshrcbash配置方式&#xff1a; ech…

海上明月共潮生【InsCode Stable Diffusion 美图活动一期】

一、 Stable Diffusion 模型在线使用地址&#xff1a; https://inscode.csdn.net/inscode/Stable-Diffusion 购买 右下角点击 Stable Diffusion WebUI 进入工作界面 二、模型相关版本和参数配置&#xff1a; 模型&#xff1a;chilloutmix_NiPrunerdFp32Fix.safetensors…

解决Python的SyntaxError: Non-UTF-8 code starting with ‘\xbb‘问题

文章目录 一、报错二、分析三、解决3.1 方法一3.2 方法二3.3 方法三 一、报错 在程序中&#xff0c;line 8为含有中文的注释&#xff0c;编译后出现了SyntaxError: Non-UTF-8 code starting with \xbb的报错&#xff0c;具体如下图。 二、分析 这个错误是由于Python解释器无…

教你如何快速批量添加滚动文字水印

在视频中添加滚动的文字水印可以增加视频的专业感和吸引力&#xff0c;但手动给大量视频添加滚动文字水印是一项费时费力的任务。下面是一个快速批量给大量视频添加滚动文字水印的方法&#xff0c;有需要的小伙伴们可以进来学习一下&#xff01; 今天分享的方法会使用到一个剪辑…

IDEA 中 gradle面板显示重复工程

使用 IDEA 构建多模块工程时&#xff0c;每新建一个模块&#xff0c;gradle 构建后&#xff0c;面板上就会出现一个重复的根工程&#xff0c;如下图红框部分&#xff0c;怎么处理下

ASIC数字设计:前端设计、验证、后端实现

前端设计 数字系统设计中有三个重要的设计级别概念&#xff1a;行为级&#xff08;Behavior Level&#xff09;、寄存器传输级&#xff08;Register Transfer Level&#xff09;和门级&#xff08;Gate level&#xff09;。其中&#xff0c; 行为级通过行为级算法描述数字系统&…

zabbix 报警测试报错

一、报错描述 在测试报警媒介时&#xff0c;报错如下: main.py脚本是在windows上的pycharm上编写的&#xff0c;在windows上运行没有问题&#xff0c;放在linux服务器上使用python3 main.py 运行也没有问题&#xff0c;但是使用./main.py执行就报错 [rootp0-tkhijbs-broadco-…

十、Docker虚悬镜像

学习参考&#xff1a;尚硅谷Docker实战教程、Docker官网、其他优秀博客(参考过的在文章最后列出) 目录 前言一、介绍二、如何产生的&#xff1f;三、尝试弄一个三、查看、删除虚悬镜像3.1 查看虚悬镜像3.2 删除虚悬镜像 总结 前言 什么是虚悬镜像&#xff1f; 一、介绍 虚悬…

【LEAP模型】能源供应转换、需求及碳排放预测、平衡表核算、模型框架构建、操作、情景设计、结果分析、优化、预测结果不确定性分析等

模型简介&#xff1a; 中文名&#xff1a;LEAP模型 外文名&#xff1a;Long Range Energy Alternatives Planning System/ Low emission analysis platform LEAP模型[1]允许研究者根据研究目的、数据可获取度、研究对象特点等灵活构建模型结构&#xff0c;十分适用于能源数据…

如何修改Jupyter Notebook的默认目录和默认浏览器

一、修改默认目录 Jupyter Notebook的文件默认保存目录是C:\Users\Administrator&#xff0c;默认目录可在黑窗口中查看&#xff0c;如下图所示&#xff1a; 为了方便文档的管理&#xff0c;可将默认目录修改成自己想保存的地方。修改方法如下&#xff1a; 1、找到config文件 …

centos搭建ftp服务器window使用ftp命令

part 1、centos搭建ftp服务器 参考链接&#xff1a;Linux搭建FTP&#xff0c;并使用Windows和IE浏览器访问FTP服务_一二三&#xff0c;开花的博客-CSDN博客 part 2、window使用ftp服务器 参考链接&#xff1a;Windows命令之ftp命令「建议收藏」-腾讯云开发者社区-腾讯云 备…

深度学习准确率提升之天花板分析

案例1 OCR文字识别流水线主要分为三个模块&#xff1a;文字检测->字符分割->字符识别 训练完成后整个系统的准确率是72%&#xff0c;需要进一步提升准确率就需要单独分析每个模块的提升空间。 1&#xff09;对于文件检测模块&#xff0c;把训练集的图像人工确保标注准…

分享一份不算优秀的HTML简历模版

一份算不上优秀的HTML简历模版 Demo http://sylvanding.github.io/cv Repo https://github.com/sylvanding/cv 示例 总结了一下本科四年都做了些什么&#xff0c;把这套模版分享给大家。

【C】数据在内存中的存储

前言 > 在内存中&#xff0c;整型和浮点型存储的方式是不同的&#xff0c;从内存中读取的方式也是有所差异的&#xff0c;这篇文章主要介绍整型和浮点型在内存中存储的方式。 整型在内存中的存储 计算机中有符号数有3种表示方式&#xff1a; 原码&#xff1a;直接将二进制按…

Material —— RBD(Houdini To UE)

目录 一&#xff0c;ABC to UE 二&#xff0c;FBX to UE 三&#xff0c;APEX to UE 四&#xff0c;VAT to UE Houdini刚体碎块导入UE&#xff0c;有两个方面需还原&#xff0c;一是材质还原&#xff0c;一是动态还原&#xff1b; 一&#xff0c;ABC to UE 材质方面&#…

医学图像超分辨率的多模态不同核大小的多头卷积注意

文章目录 Multimodal Multi-Head Convolutional Attention with Various Kernel Sizes for Medical Image Super-Resolution摘要本文方法实验结果 Multimodal Multi-Head Convolutional Attention with Various Kernel Sizes for Medical Image Super-Resolution 摘要 超分辨…

11 从0开始学PyTorch | PyTorch使用nn模块、手工实现神经网络

上一小节对神经网络有了基本的了解&#xff0c;这一小节就看一下如何用代码来实现一个神经网络。 我们所用的案例还是那个温度转换的案例&#xff0c;只不过需要我们把之前的线性模型替换成神经网络模型&#xff0c;并重新训练以找到适合神经网络的权重。 依照我们的经验&#…

PyTorch 神经网络模型可视化(Netron)

文章目录 PyTorch 神经网络模型可视化&#xff08;Netron&#xff09;ONNXtorch.savetorch.jit.scripttorch.jit.trace PyTorch 神经网络模型可视化&#xff08;Netron&#xff09; Netron 是一个用于可视化深度学习模型的工具&#xff0c;可以帮助我们更好地理解模型的结构和…

scanf大家都用过,但是scanf的多组输入!及scanf的两种输入方法你了解过嘛?看完这篇文章保证你彻底认识scanf!

☕ hello宝子们大家好啊 ,欢迎来到鸽芷咕的个人频道。 &#x1f3ac; 鸽芷咕&#xff1a; 个人主页 &#x1f525; 个人专栏: 《快速入门C语言》 《C语言初阶篇》 文章目录 前言&#x1f4ac; scanf( ) 函数的介绍&#x1f4ac; scanf( )的俩种用法&#x1f331;示例一:⛳️输入…