Android NDK系列(五)内存监控

    在日常的开发中,内存泄漏是一种比较比较棘手的问题,这是由于其具有隐蔽性,即使发生了泄漏,很难检测到并且不好定位到哪里导致的泄漏。如果程序在运行的过程中不断出现内存泄漏,那么越来越多的内存得不到释放,可用的内存越来越小,最终导致系统无法正常运行。

    本文主要介绍一种能够检测内存的方法,方便在日常的开发过程中排除程序是否存在内存泄漏的情况。

    内存泄漏主要针对在堆区分配的内存无法得到释放,在堆区分配内存的方法有malloc和new,对应释放内存为free和delete。new和delete是针对C++的,本文主要监控通过new分配的内存的情况。

   new和delete是C++语言提供的运算符,在程序可以对这两个运算符进行重载,如下所示。


void * operator new(size_t size){void *ptr = malloc(size);LOGI("new size %d  ptr %p ",size, ptr);return ptr;
}void * operator new[](size_t size){void *ptr = malloc(size);LOGI("new array size %d ptr %p ",size, ptr);return ptr;
}void operator delete(void *ptr) {LOGI("delete pointer %p",ptr);if(ptr == nullptr) return;free(ptr);
}
void operator delete[](void *ptr) {LOGI("delete array %p",ptr);if(ptr == nullptr) return;free(ptr);
}

    上面重载了new、new[],delete和delete[]四个运算符,为了验证正常使用new和delete操作能够调用以上的运算符,下面定义一个简单的类

class MEM{
public:MEM(){LOGI("MEM constructor");}~MEM(){LOGI("MEM destructor");}
private:int a;
};

    这里定义MEM类并在构造函数和析构函数加了打印,主要为了验证它们是否会被调用,下面开始使用new和delete申请和释放内存,如下所示。

LOGI("new int---------");
int *p1 = new int(3);
LOGI("new int array---------");
int *p2 = new int[5];
LOGI("new MEM object---------");
MEM * p3 = new MEM();
LOGI("new MEM object array---------");
MEM * p4 = new MEM[5];LOGI("delete p1---------");
delete p1;
LOGI("delete p2---------");
delete []p2;
LOGI("delete p3---------");
delete p3;
LOGI("delete p4---------");
delete []p4;
LOGI("---------");

上面的测试代码流程如下:

new int(3) 请求分配一个整数

new int[5] 请求分配一个数组,大小为5

new MEM() 请求分配一个MEM类型的对象

new MEM[5] 请求分配一个MEM类型的数组,大小为5

最后调用delete分别释放以上分配的内存。运行以上代码,打印结果如下。

09:57:35.596 28994-29029 Native  I  new int---------
09:57:35.596 28994-29029 Native  I  new size 4  ptr 0xdc5191c0 
09:57:35.596 28994-29029 Native  I  new int array---------
09:57:35.596 28994-29029 Native  I  new array size 20 ptr 0xdc50ed60 
09:57:35.596 28994-29029 Native  I  new MEM object---------
09:57:35.596 28994-29029 Native  I  new size 4  ptr 0xdc5191c8 
09:57:35.596 28994-29029 Native  I  MEM constructor
09:57:35.596 28994-29029 Native  I  new MEM object array---------
09:57:35.596 28994-29029 Native  I  new array size 24 ptr 0xdc50edc0 
09:57:35.596 28994-29029 Native  I  MEM constructor
09:57:35.596 28994-29029 Native  I  MEM constructor
09:57:35.596 28994-29029 Native  I  delete p1---------
09:57:35.597 28994-29029 Native  I  delete pointer 0xdc5191c0
09:57:35.597 28994-29029 Native  I  delete p2---------
09:57:35.597 28994-29029 Native  I  delete array 0xdc50ed60
09:57:35.597 28994-29029 Native  I  delete p3---------
09:57:35.597 28994-29029 Native  I  MEM destructor
09:57:35.597 28994-29029 Native  I  delete pointer 0xdc5191c8
09:57:35.597 28994-29029 Native  I  delete p4---------
09:57:35.597 28994-29029 Native  I  MEM destructor
09:57:35.597 28994-29029 Native  I  MEM destructor
09:57:35.597 28994-29029 Native  I  delete array 0xdc50edc0
09:57:35.597 28994-29029 Native  I  ---------

    通过以上log可以看到,重载的运算符new、delete,构造函数和析构函数里都走进去了,说明重载运算符是可以接管分配和释放内存的工作的,而调用构造函数和析构函数还是由编译器处理了,无须担心创建对象和销毁对象时这两个函数没有被调用。

    尽管通过重载new和delete运算符可以接管内存的分配和释放工作,但是在new操作符函数中还是无法指定是谁申请的内存,为了能确定是哪里申请的内存,需要对new操作符进行改进,如下所示。

void * operator new(size_t size,const char * file, size_t line){LOGI("new size %d file: %s line %d",size, file, line);void *ptr = malloc(size);return ptr;
}void * operator new[](size_t size,const char * file, size_t line){LOGI("new array size %d file: %s line %d",size, file, line);void *ptr = malloc(size);return ptr;
}#define new new(__FILE__,__LINE__)

    上面重新定义了new操作符,加入了文件命和行号,并且把new定义为一个宏,调用new时自动加入文件名宏和行号宏,这样在代码中调用new申请内存时自动带上对应的文件名和行号。有了文件名和行号就能知道哪个地方申请的内存。

    为了统计当前系统内存的使用请求,接下来要把内存申请的记录保存起来,这里使用一个单链表对内存的申请信息进行保存,链表的元素使用Node表示,代码如下。


typedef struct Node{void *ptr;size_t size;char *file;size_t line;struct Node *next;
} Node;Node *head = nullptr;
void addRecord(void *ptr, size_t size, const char *file, size_t line){LOGI("addRecord");Node * node = (Node *)malloc(sizeof(Node));node->ptr = ptr;node->size = size;node->file = (char *)malloc(strlen(file)+1);strcpy(node->file,file);node->line = line;node->next= nullptr;if(head == nullptr){head = node;} else{node->next = head;head = node;}
}
void removeRecord(void *ptr){if(head == nullptr) return;Node *p = head;if(p->ptr == ptr){head = head->next;if(p->file != nullptr){free(p->file);}free(p);return;}Node * q = p->next;while (q != nullptr){if(q->ptr == ptr){p->next = q->next;if(q->file != nullptr){free(q->file);}free(q);return;}p = q;q = q->next;}
}void * operator new(size_t size,const char * file, size_t line){LOGI("new size %d file: %s line %d",size, file, line);void *ptr = malloc(size);if(ptr != nullptr){addRecord(ptr, size, file, line);}return ptr;
}void * operator new[](size_t size,const char * file, size_t line){LOGI("new array size %d file: %s line %d",size, file, line);void *ptr = malloc(size);if(ptr != nullptr){addRecord(ptr, size, file, line);}return ptr;
}void operator delete(void *ptr) {if(ptr == nullptr) return;removeRecord(ptr);free(ptr);
}
void operator delete[](void *ptr) {if(ptr == nullptr) return;removeRecord(ptr);free(ptr);
}#define new new(__FILE__,__LINE__)

    链表元素使用Node表示,Node包含了申请内存的地址,大小、文件名、行号以及下一个Node的地址。

   head表示链表头。

    addRecord向链表添加记录

    removeRecord根据指针从链表中释放对应的Node。

   new运算符申请内存后向链表添加记录,delete运算符从链表删除记录后再释放内存。

      有了保存内存信息的聊吧,可以统计当前内存的使用请求,下面实现统计当前内存使用情况的快照。

int showSnapshot(){LOGI("========Memory Snapshot Begin=========");int total = 0;Node *p = head;while (p != nullptr){total += p->size;LOGI("file %s line %d allocate size %d", p->file,p->line, p->size);p = p->next;}LOGI("total memory allocate is %d", total);LOGI("========Memory Snapshot End=========");return total;
}

     在showSnapshot中,先遍历链表打印当前内存的信息,最后打印当前申请的总的内存。下面再来打印上面的测试代码的内存快照,代码如下。

int *p1 = new int(3);int *p2 = new int[5];MEM * p3 = new MEM();MEM * p4 = new MEM[5];showSnapshot();delete p1;delete []p2;delete p3;delete []p4;

    在申请完所有的内存后,调用showSnapshot打印当前内存的申请情况,如下所示。

2024-05-29 10:11:47.489 29299-29334 Native                  com.example.memory.monitor           I  ========Memory Snapshot Begin=========
2024-05-29 10:11:47.489 29299-29334 Native                  com.example.memory.monitor           I  file D:/samples/Demos/AndroidSamples/Memroy/app/src/main/cpp/main.cpp line 213 allocate size 24
2024-05-29 10:11:47.489 29299-29334 Native                  com.example.memory.monitor           I  file D:/samples/Demos/AndroidSamples/Memroy/app/src/main/cpp/main.cpp line 212 allocate size 4
2024-05-29 10:11:47.489 29299-29334 Native                  com.example.memory.monitor           I  file D:/samples/Demos/AndroidSamples/Memroy/app/src/main/cpp/main.cpp line 211 allocate size 20
2024-05-29 10:11:47.489 29299-29334 Native                  com.example.memory.monitor           I  file D:/samples/Demos/AndroidSamples/Memroy/app/src/main/cpp/main.cpp line 210 allocate size 4
2024-05-29 10:11:47.489 29299-29334 Native                  com.example.memory.monitor           I  total memory allocate is 52
2024-05-29 10:11:47.489 29299-29334 Native                  com.example.memory.monitor           I  ========Memory Snapshot End=========

     从以上的快照可以看到当前内存的申请情况,通过这些信息可以排查某个文件的某一行申请的内存是否应该释放调,由此可以判断是否出现内存泄漏的情况。

   在平常的开发中,尽可能使用智能指针,减少显示通过new申请内存的情况,这样也可以避免内存泄漏。

本示例的工程已上传到github,链接为示例工程地址

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

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

相关文章

centos时间不对

检查当前时区是否正确 timedatectl status如果时区不正确,使用以下命令设置正确的时区(将Asia/Shanghai替换为您所在的时区): timedatectl set-timezone Asia/Shanghai如果时区正确但时间不准确,使用以下命令同步网络…

Java基础语法——字符串(String/StringBuilder/Stringjoiner)

String Java的String类是不可变的,意味着一旦创建,其值就不能被改变。String类提供了丰富的API来操作字符串。 以下是一些常用的方法: 构造方法: 有以下几种常见的: public class stringlearn {public static void…

AI 赋能前端 -- 文本内容概要生成

幸福不在于你获得了什么,而在于你比他人多获得了什么 是比较出来的 大家好,我是柒八九。一个专注于前端开发技术/Rust及AI应用知识分享的Coder 此篇文章所涉及到的技术有 OpenAILangChainRust/WebAssemblyWeb Workerreact+ts+vite配置环境变量(env)因为,行文字数所限,有些概…

渗透课程第二阶段--Part1--信息收集

目录 一. 为什么要做信息收集? 渗透测试的流程 信息收集包括的内容 学习框架: 二. 分类 1. 域名相关信息 域名(Domain Name)是什么 域名的分类 域名联系人信息 子域名信息 域名DNS信息 2. IP相关信息 ping/nslookup …

香橙派OrangePi AIpro上手笔记——之USB摄像头目标检测方案测试(一)

整期笔记索引 香橙派OrangePi AIpro上手笔记——之USB摄像头目标检测方案测试(一) 香橙派OrangePi AIpro上手笔记——之USB摄像头目标检测方案测试(二) 香橙派OrangePi AIpro上手笔记——之USB摄像头目标检测方案测试&#xff0…

使用element的提示框并修改css

使用el-tooltip来做提示框&#xff1a; <el-tooltip popper-class"popper" content"敬请期待" placement"right"><div><i class"iconfont icon-lianjie-01"></i><span>输入链接</span></div&…

和鲸“101数智领航计划”——在武大解码 GeoAI,地理空间智能(GeoAI)学术研讨会及编程工作坊圆满落幕

AI 正革新地理空间数据分析的范式&#xff0c;既为科学研究及行业落地应用带来了机遇&#xff0c;同样也带来了挑战。 5 月 25 日&#xff0c;和鲸科技联合武汉大学遥感信息工程学院、中国地质大学&#xff08;武汉&#xff09;地理与信息工程学院&#xff0c;于武汉大学成功举…

element.addEventListener() 方法 详细解析与代码示例

element.addEventListener() 方法用于向指定的元素添加事件监听器。 语法: element.addEventListener(event, function, useCapture);参数说明: event&#xff1a;表示要监听的事件类型&#xff0c;比如 "click"、"mouseover"、"keydown" 等等…

使用mongorestore时no reachable servers错误的解决

使用mongorestore命令恢复导出的.bson文件至mongodb时&#xff0c;比如 mongorestore -d mydb -c mycollection mycollection.bson可能会遇到遇到类似下面的错误 Failed: error connecting to db server: no reachable servers而此时mongosh&#xff0c;或者mongo命令本身是可…

关于TeamSpeak3-网易音乐机器人的基础使用方法(胎教级教程)

本文转自博主的个人博客&#xff1a;https://blog.zhumengmeng.work,欢迎大家前往查看。 原文链接&#xff1a;点我访问 序言&#xff1a;在自己的ts服务器上安装了网易音乐机器人&#xff0c;写这篇文章旨在教群友/网友如何使用机器人!&#x1f60b;&#x1f44d; 一、TS3Audi…

Flutter 中的 SliverOverlapInjector 小部件:全面指南

Flutter 中的 SliverOverlapInjector 小部件&#xff1a;全面指南 Flutter 是一个功能丰富的 UI 框架&#xff0c;由 Google 开发&#xff0c;允许开发者使用 Dart 语言构建跨平台的移动、Web 和桌面应用。在 Flutter 的滚动视图系统中&#xff0c;SliverOverlapInjector 是一…

Nginx平滑升级

目录 编译安装nginx1.18 平滑升级至1.22版本 本文记录了编译安装的nginx1.18平滑升级到nginx1.22版本的流程&#xff0c;都采用的编译安装&#xff0c;有不清楚流程的可以评论区我&#xff0c;咱们一起讨论。 编译安装nginx1.18 # 安装编译依赖 yum -y install gcc pcre pc…

揭秘Python:下划线的特殊用法,你绝对想不到!

在Python编程中&#xff0c;下划线&#xff08;underscore&#xff09;是一个常见而又强大的工具。它不仅仅是一个普通的字符&#xff0c;而是具有特殊含义和用法的符号。今天&#xff0c;我们就来揭开Python下划线的神秘面纱&#xff0c;探索它的各种妙用。 下划线的基本用法…

01--nginx基础

前言&#xff1a; 本文用来整理一下nginx的用法&#xff0c;应该是本人中间件专栏的第一篇文章&#xff0c;这里开始概念和实操将会同样重要&#xff0c;面试时基本概念的理解非常重要&#xff0c;深有体会&#xff0c;不会再让概念成为压死骆驼的稻草。 1、nginx简介 Nginx…

3、css3 手写nav导航条(互相学习)

效果例图&#xff1a; 1、首先呈现的是html代码&#xff1a; <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><…

Keras深度学习框架基础第三讲:层接口(layers API)“层权重正则化器”

1、绪论 在Keras中&#xff0c;层权重正则化器是用于防止模型过拟合的一种技术&#xff0c;它通过向模型的损失函数中添加一个与权重相关的惩罚项来实现。 1.1 Keras正则化器概述 正则化器的概念&#xff1a; 正则化器允许在优化过程中对层的参数或层的激活情况进行惩罚。网络…

ARM32开发——库与包

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 嵌入式软件通用架构库SPL库HAL库MSP 嵌入式软件通用架构 库 标准外设库&#xff08;Standard Peripheral Library&#xff09;和H…

【c#跨线程操作界面方法】

我们知道,在winform中进行跨线程操作,如果处理不好则会出现界面卡顿、“线程间操作无效: 从不是创建控件的线程访问它“等问题,在此提供跨线程操作界面方法,可以很好解决相关问题。 一、 通过设置UI控件的Invoke和BeginInvoke方法实现更新,在子线程中调用如下函数更改主界…

Kafka重平衡导致无限循环消费问题

1. 问题描述 Kafka消费者消费消息超过了5分钟&#xff0c;不停的触发重平衡&#xff0c;消费者的offset因为重平衡提交失败&#xff0c;重复拉取消费&#xff0c;重复消费。 2. 问题原因 kafka默认的消息消费超时时间max.poll.interval.ms 300000, 也就是5分钟&#xff0c;…

Python魔法之旅-魔法方法(03)

目录 一、概述 1、定义 2、作用 二、主要应用场景 1、构造和析构 2、操作符重载 3、字符串和表示 4、容器管理 5、可调用对象 6、上下文管理 7、属性访问和描述符 8、迭代器和生成器 9、数值类型 10、复制和序列化 11、自定义元类行为 12、自定义类行为 13、类…