在wikipedia这样解读内存泄漏的:在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。
内存不是无穷无尽的,是有限的,如果申请了,使用了,用完没有释放,那么这块内存就一直无法被重新利用,最后再申请内存就找不到空闲的了(称为out of memory,OOM),可能导致程序逻辑错误崩溃。
以下用一个简单的例子说明内存泄漏:
#include <stdio.h>
#include <stdlib.h>int main(int argc, char *argv[])
{char *p[1000];int i;for (i = 0; i < 1000; i++) {p[i] = malloc(i * 2 + 100);if (p[i])memset(p[i], 0, i * 2 + 100);}for (i = 0; i < 1000; i++) {printf("%p\n", p[i]);}return 0;
}
上面的程序malloc申请的内存直至退出都没有被释放,这个就是内存泄漏了。
分类
1.内存泄漏可以根据发生的方式来分类:
常发性泄漏
故障代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
偶发性泄漏
故障代码只有在特定场景或操作过程下才会被执行。常发性和偶发性是相对的。对于特定场景,偶发性的也许就变成了常发性的。所以测试场景和方法对检测内存泄漏至关重要。
一次性泄漏
故障代码只会被执行一次或者由于逻辑的缺陷导致只有一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存。
隐式泄漏
程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但对于daemon或需要长时间运行的程序来讲,不及时释放可能最终耗尽所有内存。
2.还可以根据泄漏类型来分类:
未引用泄漏
这些泄漏点,已无任何指针指向,因此无法被找到,永远无法被释放。native/kernel有这类问题,而java无此类问题,因为没被找到的,都可以gc。引用泄漏 可以被找到,等于隐式泄漏。
引用泄漏
像是缓存没有控制上限而导致耗尽内存。如果控制的好,则没问题,比如kernel里的slub也有per cpu缓存。java也有这种问题存在。另外不同的软件层都有可能存在内存泄漏:kernel层、native层和java层。不同的软件层泄漏的原理都差不多。
危害
内存泄漏是较难检测的异常之一,除了常发性泄漏,其他都是难以检查到的。从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为用户无法感觉到内存泄漏的存在,除非泄漏大量内存或一直累积直至消耗光内存,这时就有各种明显表现:
性能渐渐变差,因为要做各种内存回收工作。
直接出现逻辑错误崩溃,没有做好异常处理(error handling)。
本身没有问题,却引起其他程序异常。既然内存泄漏这么难处理,那么是否有办法自动回收,不需要编程人员管理申请的内存呢?有的,java语言就有内存回收机制(垃圾回收,gc)。
不过即使用java编写的程序也可能出现泄漏,后面会讲解java内存泄漏的原理和调试方法。
通用的调试方法
需要对每一块申请的内存做标记,记录调用栈等信息,然后监控内存用量信息,感觉已经超出正常的内存用量很多时(相差不大时不见得能查到问题),提取这些信息,然后分析信息,找出可能的泄漏点,检查代码,分析逻辑,修复问题。
回复「 篮球的大肚子」进入技术群聊
回复「1024」获取1000G学习资料