【内存泄漏】内存泄漏及常见的内存泄漏检测工具介绍

内存泄漏介绍

什么是内存泄漏

内存泄漏是指程序分配了一块内存(通常是动态分配的堆内存),但在不再需要这块内存的情况下未将其释放。内存泄漏会导致程序浪费系统内存资源,持续的内存泄漏还导致系统内存的逐渐耗尽,最终导致程序或系统崩溃。

内存泄漏和常驻内存区别

常驻内存(Resident Set)是指进程在运行期间占用的内存大小,包括进程使用的代码、数据和其他资源。常驻内存是进程在运行期间一直驻留在内存中的部分,即使在进程不活动时也不会被释放。
常驻内存通常不会带来显著的负面影响。

程序与进程里的内存布局

下图是源码与 ELF(可执行可链接) 文件以及运行起来后内存布局的简易映射关系图。

程序中的初始化全局变量和局部静态变量被编译到 .data,未初始化的全局变量和局部静态变量编译后放在 .bss 段,代码主体和函数主题存放在 .text 段,ELF 文件内实际有很多段(参考《程序员的自我修养-链接,装载与库》第三章)。
当程序运行时,会将 ELF 文件加载到内存。不同的段会加载到内存布局中的不同位置,其中 heap 这部分就是程序员手动去动态申请和释放内存的部分。当程序员用 malloc 函数申请了一块内存,使用完之后却没有 free 它的时候,就会发生内存泄漏。内存泄漏得越多,进程中可以使用内存的空间就越少,时间长了就会导致系统响应慢,甚至程序崩溃。

如何“观察”内存泄漏是否发生?

在 Android 系统上通常可以用 dumpsys meminfo 命令查看进程的内存使用数据,重复 dump 后从数据的变化情况来大致判断是否有内存泄漏。

也可以借助python 或者其他一些工具将数据可视化方便查看数据变化趋势。

但这只能大致的给你展示数据变化的趋势,而非直接明白的告诉你是否发生了内存泄漏。因此我们需要更精确的工具来检测是否也有内存泄漏。

常见的内存检测工具介绍

本节我们将依次介绍 Malloc Debug, libmemunreachable, Asan, HwASan, MTE, Heapprofd, Memcheck(Valigrind)
内存泄漏检测工具(https://source.android.com/docs/core/tests/debug/native-memory?hl=zh-cn)

Malloc Debug

简介

Malloc Debug 是一种调试本机内存问题的方法。 它可以帮助检测内存损坏、内存泄漏和释放后使用问题。
Malloc Debug 通过对常规的 allocation 函数包装了一层来记录和分析内存的申请和释放。这些函数包括:malloc, free, calloc, realloc, posix_memalign, memalign, aligned_alloc, malloc_usable_size

使用方法

运行程序前的设置

adb shell setprop libc.debug.malloc.options "\"backtrace guard leak_track backtrace_dump_on_exit backtrace_dump_prefix=/sdcard/heap"\"
adb shell setprop libc.debug.malloc.program xxx(进程名)

参数介绍:

  • backtrace: 开启堆栈记录。
  • guard: 开启内存越界检测。
  • leak_track: 程序在退出时,如有内存泄漏,而不产生abort。
  • backtrace_dump_on_exit: 程序退出时dump堆栈和内存信息。
  • backtrace_dump_prefix: dump文件存放的路径和文件名的开头字符。如本处生成的文件放在/sdcard/目录下,文件名开头为heap字样,注意指定的路径要有写权限。
  • libc.debug.malloc.program: 用于设置检测的程序,不设置则检测所有的运行的程序。

执行待测试程序

  1. 离线程序
    离线程序运行完成后会在 backtrace_dump_prefix 设定的路径下存储 dump 文件
  2. 在线程序
    需要先停掉程序所在的进程,再重启该进程才会生效。
    由于在线程序一般不会主动退出(如 camerahalserver),需要使用命令来主动触发 dump。
    命令:kill -47 xxx(进程ID),注意多次触发新文件覆盖之前的文件。
    当你的程序有内存泄漏问题的话,输出如下报告:
E malloc_debug: +++ memtest leaked block of size 48 at 0x7a4a6a42e0 (leak 1 of 2)
E malloc_debug: Backtrace at time of allocation:
E malloc_debug:           #00  pc 000000000004461c  /apex/com.android.runtime/lib64/bionic/libc.so (malloc+76)
E malloc_debug:           #01  pc 00000000000c83e8  /apex/com.android.runtime/lib64/bionic/libc.so (__register_atfork+40)
E malloc_debug:           #02  pc 000000000005460c  /apex/com.android.runtime/lib64/bionic/libc.so
E malloc_debug:           #03  pc 00000000000613a0  /apex/com.android.runtime/bin/linker64
E malloc_debug:           #04  pc 0000000000061144  /apex/com.android.runtime/bin/linker64
E malloc_debug:           #05  pc 0000000000061144  /apex/com.android.runtime/bin/linker64
E malloc_debug:           #06  pc 00000000000d5f14  /apex/com.android.runtime/bin/linker64
E malloc_debug:           #07  pc 00000000000d4e0c  /apex/com.android.runtime/bin/linker64
E malloc_debug:           #08  pc 0000000000064004  /apex/com.android.runtime/bin/linker64
E malloc_debug: +++ memtest leaked block of size 20 at 0x793a6ae9a0 (leak 2 of 2)
E malloc_debug: Backtrace at time of allocation:
E malloc_debug:           #00  pc 000000000004461c  /apex/com.android.runtime/lib64/bionic/libc.so (malloc+76)
E malloc_debug:           #01  pc 00000000000100b8  /data/local/tmp/memtest/memtest
E malloc_debug:           #02  pc 00000000000546e8  /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+104)
E malloc_debug: Dumping to file: /sdcard/heap.19748.exit.txt

注意报告中并不是所有的 leak 都是真正的内存泄漏,有些可能是常驻内存,开发者需要自己判断。
还需注意dump 路径要有写权限

很多时候在线运行环境下so 是无符号的程序,我们需要解析 dump 文件定位代码行号
python3 native_heapdump_viewer.py --symbols ./symboldir/ ./heap.4169.exit.txt --html > memtest4169.html

–symbols 指定的是符号库/程序的路径,子目录的路径必须要在手机上的路径一致。比如可执行程序在手机里的路径是/vendor/bin/memtest,那解释时它的带符号的程序路径上需要是 ./symboldir/vendor/bin/memtest

检测出来的并不是都是泄漏,一部分是属于常驻内存,尤其对于在线程序,我们需要将程序运行不同的次数,抓出不同的log来做对比,找出真正增长的部分。

libmemunreachable

简介

Android 的 libmemunreachable 是一个零开销的本地内存泄漏检测器。 它会在触发内存检测的时候遍历进程内存,同时将任何不可访问的块报告为泄漏。

命令行方式使用

设置属性

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

参数

  • backtrace_size 只收集泄漏指定 size 大小的 backtrace
  • backtrace_min_size=192 backtrace_max_size=320 收集泄漏 size 介于两者之间的backtrec

重启应用,执行 dumpsys -t 600 meminfo --unreachable [process].(自测没有 dump 出预期结果)。下面是一个带有内存问题的输出结果。

 Unreachable memory
  24 bytes in 2 unreachable allocations
  ABI: 'arm64'

  24 bytes unreachable at 71d37787d0
   first 20 bytes of contents:
   71d37787d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
   71d37787e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

  24 bytes unreachable at 71d37797d0
   first 20 bytes of contents:
   71d37797d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
   71d37797e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

添加代码方式使用

官方提供4个接口来检测内存
C interface

  • bool LogUnreachableMemory(bool log_contents, size_t limit)
  • bool NoLeaks()

C++ interface

  • bool GetUnreachableMemory(UnreachableMemoryInfo& info, size_t limit = 100)
  • std::string GetUnreachableMemoryString(bool log_contents = false, size_t limit = 100)

核心函数是 GetUnreachableMemory() 其他三个函数内部都会调用此函数。
在使用添加代码的方式打印时,需要在编译代码时需要将 libmemunreachable.so 添加到动态依赖,libmemunreachable.so 文件可以在手机 /system/lib64/libmemunreachable.so 获取。

例子:
以下是一个包含内存泄漏的例子,在f 函数中申请了x, y 两块内存,在函数返回前x 被释放,y 赋值后没有被释放。

#include "./memunreachable.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

using namespace android;

void f(void);
void f(void) {
  printf("[memtest] function f\n");
  int* x = (int*)malloc(10 * sizeof(int));
  x[0] = 0;
  int* y = (int*)malloc(5 * sizeof(int));
  y[0] = 0;
  y[1] = 1;
  y[2] = 2;
  y[3] = 3;
  y[4] = 4;
  free(x);
}

int main(void) {
  printf("[memtest] hello main\n");
  f();

  // C interface
  printf("LogUnreachableMemory()\n");
  LogUnreachableMemory(true, 100);

  return 0;
}

adb log 输出(考虑排版省去时间戳)
log 里显示有一个 20 bytes 的内存泄漏,20 正是5 个 int 的大小,对应申请但没有释放的 y 地址的内存。

// 新建 Collection process
31232 31231 I libmemunreachable: collecting thread info for process 31231...
31232 31231 I libmemunreachable: collection thread done
// fork 进程运行 sweeping process 
31233 31233 I libmemunreachable: searching process 31231 for allocations
31233 31233 I libmemunreachable: searching done
31233 31233 I libmemunreachable: sweeping process 31231 for unreachable memory
31233 31233 I libmemunreachable: sweeping done
31233 31233 I libmemunreachable: folding related leaks
31233 31233 I libmemunreachable: folding done
// 回到 Original process 接收检测结果
31231 31231 I libmemunreachable: unreachable memory detection done
31231 31231 E libmemunreachable: 20 bytes in 1 allocation unreachable out of 1260 bytes in 7 allocations
31231 31231 E libmemunreachable:   20 bytes unreachable at 7a03454400
31231 31231 E libmemunreachable:    contents:
31231 31231 E libmemunreachable:    7a03454400: 00 00 00 00 01 00 00 00 02 00 00 00 03 00 00 00 ................
31231 31231 E libmemunreachable:    7a03454410: 04 00 00 00                                     ....
31231 31231 E libmemunreachable:           #00  pc 000000000003e238  /apex/com.android.runtime/lib64/bionic/libc.so (malloc+84)
31231 31231 E libmemunreachable:           #01  pc 00000000000100b8  /data/local/tmp/memtest/memtest_libmemunreachable
31231 31231 E libmemunreachable:           #02  pc 000000000004aa48  /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+100)

调用 bool LogUnreachableMemory(bool log_contents, size_t limit) 时,log_contents 传 true 会打印泄露地址的内容,也就是 contents 对应的两行内容。之后可以用 address2line 解析行号。

其他内存检测工具简介

ASan

  • Asan(AddressSanitizer) 适用于检测内存越界访问、缓冲区溢出、内存泄漏等问题。它是一个在编译时插入的工具;
  • 运行时有一定的性能开销(约增加两倍),代码大小和内存均有额外开销;
  • 需要重新编译程序,编译时添加 address 相关选项;
  • 可用于 linux 和 android,但在 android 上逐步被 HwASan 取代;
  • 需要刷与 ASan 兼容的 ROM;
  • 不再受支持,即使有bug 也不会修复;

HWASan

  • HWASan 利用硬件特性,适用于检测内存错误,类似于 ASan,但能够更高效地运行在一些支持硬件特性的平台上。
  • 性能开销和 Asan 接近,但内存占用更小;
  • 需要重新编译程序,编译时添加 hwaddress 相关选项
  • 仅适用于 Android 10 及更高版本,AArch64 硬件;
  • 需要刷与 HWASan 兼容的 ROM;

MTE

  • MTE(Memory Tagging Extension) 使用硬件标签来检测内存错误,主要专注于检测内存越界访问。
  • 提供了较低的性能开销,首次具备了线上部署的可能。
  • 无需重新构建代码来检测堆错误(但需要重新构建代码来检测堆栈错误)
  • Android 系统在 Arm v9 上开始支持,仅适用于64位应用/程序;

Heapprofd

  • Heapprofd 是一个跟踪给定时间段内 Android 进程的堆分配和释放的工具。
  • 可以借助 Perfetto 抓取,开发人员可以使用该工具调查内存问题(调用栈和内存分配)。

    当开启连续 dump 后,开发者可以查看程序结束前内存占用是否合理,以检查是否有潜在内存泄漏问题。或者将待测试代码循环执行,比较每执行一次代码段后内存是否有增加,一次判断是否有内存泄漏。

Valgrind 中的Memcheck

  • Memcheck 是 Valgrind 工具套件中的一个工具,用于检测 C 和 C++ 程序中的内存错误。
  • 内存问题检测比较全面,但对性能影响比较大,耗时增加10x~20x,不适用对时间敏感的程序。
  • 在 Ubuntu 上安装:sudo apt-get install valgrind

使用 memcheck 的基本方法

  • 编译程序时加上 –g 选项,编译优化选项建议选择 -O1;
  • 使用 Valgrind 命令运行程序:valgrind --leak-check=yes myprog arg1 arg2valgrind 使用 --tools 来指定 debug 工具,而 Memcheck 是默认工具,可以省略 --tools=memcheck 选项;
  • 程序运行后输出问题报告。
$ valgrind --leak-check=yes ./memtest_origin 
==19517== Memcheck, a memory error detector
==19517== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==19517== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==19517== Command: ./memtest_origin
==19517== 
[memtest] hello main
[memtest] function f
==19517== 
==19517== HEAP SUMMARY:
==19517==     in use at exit: 20 bytes in 1 blocks
==19517==   total heap usage: 3 allocs, 2 frees, 1,084 bytes allocated
==19517== 
==19517== 20 bytes in 1 blocks are definitely lost in loss record 1 of 1
==19517==    at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==19517==    by 0x1086FF: f() (memtest_origin.cc:9)
==19517==    by 0x108731: main (memtest_origin.cc:16)
==19517== 
==19517== LEAK SUMMARY:
==19517==    definitely lost: 20 bytes in 1 blocks
==19517==    indirectly lost: 0 bytes in 0 blocks
==19517==      possibly lost: 0 bytes in 0 blocks
==19517==    still reachable: 0 bytes in 0 blocks
==19517==         suppressed: 0 bytes in 0 blocks
==19517== 
==19517== For counts of detected and suppressed errors, rerun with: -v
==19517== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

总结

本文我们介绍了内存泄漏的概念,malloc debug 和 libmemunreachable 的使用方法,以及一些其他内存检测工具的简介,下一篇我们将介绍 malloc debug 和 libmemunreachable 的工作原理。

参考链接

  1. 【内存】Android C/C++ 内存泄漏分析 unreachable
  2. 调试本地内存使用  |  Android 开源项目  |  Android Open Source Project
  3. 调试和减少内存错误  |  Android NDK  |  Android Developers (google.cn)
  4. Malloc Debug (googlesource.com)
  5. Malloc Hooks (googlesource.com)
  6. libmemunreachable (googlesource.com)
  7. Memcheck: a memory error detector
  8. Heap profiler - Perfetto Tracing Docs

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】进程周边007之进程控制

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 1.进程创建 2.进程终止 2.…

python调用DALL·E绘画

实现用gpt的api和他对话后&#xff0c;我们试着调用DALLE的api进行绘画 参考文档 OpenAI API 运行代码 from openai import OpenAIclient OpenAI()user_prompt input("请输入您想生成的图片描述: ")response client.images.generate(model"dall-e-3"…

SpringIOC之SimpleTimeZoneAwareLocaleContext

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

CSS新手入门笔记整理:CSS3弹性盒模型

特点 子元素宽度之和小于父元素宽度&#xff0c;所有子元素最终的宽度就是原来定义的宽度。子元素宽度之和大于父元素宽度&#xff0c;子元素会按比例来划分宽度。在使用弹性盒子模型之前&#xff0c;必须为父元素定义“display:flex;”或“display:inline-flex;”。 弹性盒子…

Linux笔记本电脑投屏到电视,用网页浏览器就能投屏到电视!

Linux系统的电脑如果要投屏到安卓电视屏幕上&#xff0c;可以使用投屏工具AirDroid Cast的网页版和TV版一起实现。 首先&#xff0c;在Linux系统的电脑里用chrome浏览器或edge浏览器打开网址webcast.airdroid.com。这个网址就是AirDroid Cast的网页版。你可以看到中间白色框框的…

一文搞懂 java8 reduce操作

什么是 reduce Java8 中有两大最为重要的改变&#xff0c;其一是 Lambda 表达式&#xff0c;另一个就是 Stream API 了。 Stream 是 Java8 中处理集合的关键抽象概念&#xff0c;它将数据源流化后&#xff0c;可以执行非常复杂的查找、过滤和映射数据、排序、切片、聚合统计等…

中国化妆品头部企业环亚集团携美肤宝、法兰琳卡、滋源、肌肤未来等“新朋友”加入实在智能数智生态圈

广州环亚化妆品科技股份有限公司&#xff08;以下简称“环亚集团”&#xff09;是一家综合性美容化妆品高新技术企业&#xff0c;旗下拥有美肤宝、法兰琳卡、滋源、肌肤未来等多个品牌&#xff0c;产品涵盖洁肤护肤、洗护发、身体护理、精油等多个领域。在中国、澳大利亚、美国…

27 redis 的 sentinel 集群

前言 redis 的哨兵的相关业务功能的实现 哨兵的主要作用是 检测 redis 主从集群中的 master 是否挂掉, 单个哨兵节点识别 master 下线为主管下线, 超过 quorum 个 哨兵节点 认为 master 挂掉, 识别为 客观下线 然后做 failover 的相关处理, 重新选举 master 节点 我们这里…

【C++刷题】前缀和

【C刷题】前缀和 一、前缀和1、题目链接2、解析3、代码 二、二位前缀和1、题目链接2、解析3、代码 三、寻找数组的中心下标1、题目链接2、解析3、代码 四、除自身以外数组的乘积1、题目链接2、解析3、代码 五、和为K的子数组1、题目链接2、解析3、代码 六、和可被K整除的子数组…

Linux Shell 001-Bash简介

Linux Shell 001-Bash简介 本节关键字&#xff1a;Linux、Bash Shell、shell分类 相关指令&#xff1a;bash、sh、cat Shell的介绍 计算机只能认识&#xff08;识别&#xff09;机器语言(0和1)&#xff0c;如&#xff08;11000000 这种&#xff09;。但是&#xff0c;我们的…

手写MapReduce实现WordCount

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 文章目录 需求分析编写MapReduce实现上述功能Mapper类Reducer类Driver类 查看输出结果 需求 假设有一个文本文件word.txt&#xff0c;我们想要统计这个文本文件中每个单词出现的次数。 文件内容如下…

【内存泄露】记一次内存泄露排查,罪魁祸首是HttpClient

📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!📢本文作者:由webmote 原创📢作者格言:新的征程,我们面对的不仅仅是技术还有人心,人心不可测,海水不可量,唯有技术,才是深沉黑夜中的一座闪烁的灯塔 !序言 很久很久以前,曾经的青葱…

docker构建镜像及项目部署

文章目录 练习资料下载一、docker基础1. 基本概念2. docker常见命令3. 命令别名4. 数据卷 二、docker自定义镜像1. 了解镜像结构2. 了解Dockerfile3. 构建Dockerfile文件&#xff0c;完成自定义镜像 三、网络1. docker常见网络命令2. docker自带虚拟网络3. 自定义网络 四、dock…

【3D Max】入门

文章目录 概述界面介绍常用功能保存和导入基本建模编辑模型材质和贴图光源和阴影动画制作渲染设置导出和打印来源 概述 3 ds MAX是由 Discreet (后来被 Autodesk (Autodesk)合并)开发的一款基于 PC系统的3 d Max或3 ds MAX三维动画绘制和制作软件&#xff0c;其主要功能有建模…

【精选】vulnhub CTF6 linux udev提权 (青铜门笔记)

一、信息收集 1.主机探测 发现靶机的IP地址是&#xff1a;192.168.103.130 ┌──(root&#x1f480;kali)-[~] └─# arp-scan -l2.访问web页面 发现有个登录的页面&#xff0c;尝试了弱口令&#xff0c;但是发现没有成功&#xff1b; 所以&#xff0c;我们需要在后面的信…

Go 泛型发展史与基本介绍

Go 泛型发展史与基本介绍 Go 1.18版本增加了对泛型的支持&#xff0c;泛型也是自 Go 语言开源以来所做的最大改变。 文章目录 Go 泛型发展史与基本介绍一、为什么要加入泛型&#xff1f;二、什么是泛型三、泛型的来源四、为什么需要泛型五、Go 泛型设计的简史六、泛型语法6.1 …

Netty RPC 实现(二)

Netty RPC 实现 概念 RPC&#xff0c;即 Remote Procedure Call&#xff08;远程过程调用&#xff09;&#xff0c;调用远程计算机上的服务&#xff0c;就像调用本地服务一样。RPC 可以很好的解耦系统&#xff0c;如 WebService 就是一种基于 Http 协议的 RPC。这个 RPC 整体…

机器视觉兄弟们,出身寒微,不是耻辱,能屈能伸,方为丈夫

人生过往&#xff0c;当时只道是寻常。 可以说&#xff0c;“社会边角料”这个词&#xff0c;即刺耳&#xff0c;又是那么难听。只是&#xff0c;无数的年轻人和中年人&#xff0c;都喜欢用这个词来自嘲。 特别是出身寒微&#xff0c;没啥资源的80后和90后&#xff0c;他们总是…

C语言的分支和循环语句

各位少年&#xff0c;今天和大家分享的是分支语句循环体语句&#xff0c;C语言是结构体的程序设计语言&#xff0c;这里的结构指的是&#xff08;顺序结构&#xff09;&#xff08;选择结构&#xff09;&#xff08;循环结构&#xff09;C语言是能够实现这三种结构的&#xff0…

docker容器内 获取宿主机ip

可以使用命令 --add-host jargatewayip:192.168.0.47 \ 需要注意,这里不能是 127.0.0.1 ,所以要找到服务器局域网的ip 命令示例 docker run -it \-p 80:80 \-p 443:443 \--name nginx \--network app --hostname nginx \-e TZAsia/Shanghai \--add-host jargatewayip:192.16…