首页 > 编程知识 正文

内存泄漏的原因及解决办法,内存泄漏排查工具Java

时间:2023-05-03 23:12:58 阅读:221596 作者:3104

本文主要对内存泄漏、内存溢出、内存踩踏【踩内存】以及malloc的底层实现原理进行了总结。话不多说,直接往下看:

参考文章:

内存泄漏与内存溢出:

https://blog.csdn.net/ruiruihahaha/article/details/70270574

踩内存:

https://blog.csdn.net/u014485485/article/details/104714934

malloc的底层实现原理:

https://blog.csdn.net/ypshowm/article/details/89071869

目录 内存泄漏与内存溢出内存泄漏与内存溢出的概念:内存泄漏与内存溢出二者的关系内存泄漏的分类(按发生方式分类) 内存踩踏【踩内存】踩内存的概念出现踩内存的情形如何排查踩内存bug如何避免踩内存bug: malloc的底层实现原理

内存泄漏与内存溢出 内存泄漏与内存溢出的概念:

内存泄漏(memory leak):

是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏不断堆积后,就会造成内存溢出。

内存溢出(out of memory):

指程序申请内存时,没有足够的内存供申请者使用。通俗理解就是内存不够,通常在运行大型软件或游戏时,软件或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。

内存泄漏与内存溢出二者的关系

内存泄漏的堆积最终会导致内存溢出。

**内存泄漏:**你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。

就相当于你租了个带钥匙的柜子,你存完东西之后把柜子锁上之后,把钥匙丢了或者没有将钥匙还回去,那么结果就是这个柜子将无法供给任何人使用,也无法被垃圾回收器回收,因为找不到他的任何信息。

**内存溢出:**通俗理解就是内存不够,通常在运行大型软件或游戏时,软件或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。

一个盘子用尽各种方法只能装4个果子,你装了5个,结果掉倒地上不能吃了。这就是溢出。比方说栈,栈满时再做进栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称为下溢。就是分配的内存不足以放下数据项序列,称为内存溢出。说白了就是我承受不了那么多,那我就报错。

内存泄漏的分类(按发生方式分类) **常发性内存泄漏:**发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。**偶发性内存泄漏:**发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。**一次性内存泄漏:**发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。**隐式内存泄漏:**程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。 内存踩踏【踩内存】 踩内存的概念

访问了不合法的地址 。

通俗一点就是访问了不属于自己的地址。如果这块地址分配给了另一个变量使用,就会破坏别人的数据。从而导致程序运行异常,挂死,输出图像破图等。

出现踩内存的情形

内存访问越界

a) 数组访问越界;

b) 字符串操作越界;

非法指针

a) 使用了空指针;

b) 使用了释放掉的指针;

c) 指针类型转换错误;

栈溢出;

多线程读写的数据没有加锁保护;

多线程使用了线程不安全的函数。

如何排查踩内存bug 删减模块代码,缩小排查范围;加log,打印内存地址/值;将被踩内存设置成只读,在调用栈中查看谁在写;

还有一些结合内核分析的高级手段,待学习。

如何避免踩内存bug:

写代码时严格遵守编程规范!!!

数组访问边界检查,防止溢出,关于数组大小等关键数值使用宏代替;

使用strcpy,strcat,sprintf,strcmp,strcasecmp等字符串操作函数需谨慎,可以以使用strncpy,strlcpy,strncat,strlcat,snprintf,strncmp,strncasecmp等代替防止读写越界;

memcpy在内存重叠时不保证拷贝是正确的,memmove在内存重叠时保证拷贝是正确的;

指针释放后及时置空,必要时使用指针非空检查;

不要定义过大的局部变量,例如数组,容易造成栈溢出;

变量定义好后及时赋初值,特别像结构体这种;

代码嵌套适量,防止资源重复申请/释放;

多线程时做好线程保护;

踩内存定位的成本很高。一旦写出内存踩踏的代码,排查的成本是按规范写代码的成本的1000x。

注:

在相机系统中,出现系统崩溃或者图像损坏(绿条,粉条,马赛克等)可以检查一下是否有踩内存,但有一种情况就是同一帧的图像出现块状的错位拼接,大片规则粉块,很有可能是由DDR带宽不够,造成某个模块的ddr lantency太大造成的,需要谨慎查找。

malloc的底层实现原理

malloc 函数的实质是它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。

调用 malloc()函数时,它沿着连接表寻找一个大到足以满足用户请求所需要的内存块。(如果没有搜索到,那么就会用sbrk()才推进brk指针来申请内存空间)。

然后,将该内存块一分为二(一块的大小与用户申请的大小相等,另一块的大小就是剩下来的字节)。 接下来,将分配给用户的那块内存存储区域传给用户,并将剩下的那块(如果有的话)返回到连接表上。

调用 free 函数时,它将用户释放的内存块连接到空闲链表上。

到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段, 那么空闲链表上可能没有可以满足用户要求的片段了。于是,malloc() 函数请求延时,并开始在空闲链表上检查各内存片段,对它们进行内存整理,将相邻的小空闲块合并成较大的内存块。

搜索空闲块最常见的算法有:首次适配,下一次适配,最佳适配。 (其实就是操作系统中动态分区分配的算法)

首次适配:第一次找到足够大的内存块就分配,这种方法会产生很多的内存碎片。下一次适配:也就是说等第二次找到足够大的内存块就分配,这样会产生比较少的内存碎片。最佳适配:对堆进行彻底的搜索,从头开始,遍历所有块,使用数据区大小大于size且差值最小的块作为此次分配的块。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。