引言
内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果
内存泄漏缺陷具有隐蔽性、积累性的特征,比其他内存非法访问错误更难检测;因为内存泄漏的产生原因是内存块未被释放,属于遗漏型缺陷而不是过错型缺陷;此外,内存泄漏通常不会直接产生可观察的错误症状,而是逐渐积累,降低系统整体性能,极端的情况下可能使系统崩溃
随着计算机应用需求的日益增加,应用程序的设计与开发也相应的日趋复杂,开发人员在程序实现的过程中处理的变量也大量增加,如何有效进行内存分配和释放,防止内存泄漏的问题变得越来越突出
例如:服务器应用软件,需要长时间的运行,不断的处理由客户端发来的请求,如果没有有效的内存管理,每处理一次请求信息就有一定的内存泄漏;这样不仅影响到服务器的性能,还可能造成整个系统的崩溃。因此,内存管理成为软件设计开发人员在设计中考虑的主要方面
内存泄漏的原因
- 从变量存在的时间生命周期的角度上,把变量分为静态存储变量和动态存储变量两类;静态存储变量是指在程序运行期间分配了固定存储空间的变量。动态存储变量是指在程序运行期间根据实际需要进行动态地分配存储空间的变量
- 程序中所用的数据分别存放在静态存储区和动态存储区中;静态存储区数据在程序的开始就分配好内存区,在整个程序执行过程中它们所占的存储单元是固定的,在程序结束时就释放,因此静态存储区数据一般为全局变量
- 动态存储区数据则是在程序执行过程中根据需要动态分配和动态释放的存储单元,动态存储区数据有三类函数形参变量、局部变量和函数调用时的现场保护与返回地址;由于动态存储变量可以根据函数调用的需要,动态地分配和释放存储空间,大大提高了内存的使用效率,使得动态存储变量在程序中被广泛使用。
开发人员进行程序开发的过程使用动态存储变量时,不可避免地面对内存管理的问题。程序中动态分配的存储空间,在程序执行完毕后需要进行释放。没有释放动态分配的存储空间而造成内存泄漏,是使用动态存储变量的主要问题。一般情况下,开发人员使用系统提供的内存管理基本函数,如 malloc 、 recalloc 、 calloc 、 free 等,完成动态存储变量存储空间的分配和释放
但是,当开发程序中使用动态存储变量较多和频繁使用函数调用时,就会经常发生内存管理错误
例如:
- 分配一个内存块并使用其中未经初始化的内容;
- 释放一个内存块,但继续引用其中的内容;
- 子函数中分配的内存空间在主函数出现异常中断时、或主函数对子函数返回的信息使用结束时,没有对分配的内存进行释放;
- 程序实现过程中分配的临时内存在程序结束时,没有释放临时内存。内存错误一般是不可再现的,开发人员不易在程序调试和测试阶段发现,即使花费了很多精力和时间,也无法彻底消除
内存泄漏通常分为一下四类
1)常发性内存泄漏
发生内存泄漏的代码会被多次执行,每行一次执行都会导致一块内存泄漏
2)偶发性内存泄漏
发生内存泄漏的代码只在某些特定的环境或操作中才会发生,常发性和偶发性是相对的,在特定的环境下,偶发性内存泄漏也许就变成了常发性
3)一次性内存泄漏
发生内存泄漏的代码只被执行一次
4)隐式内存泄漏
程序在运行过程中不停的分配内存,但直到结束时才释放内存,严格的说,并没有发生内存泄漏,因为程序最终释放了内存,但是在服务器上一个程序,通常运行时间长,不及时释放内存也可能导致内存耗尽;这类被称为隐式内存泄漏
内存分配策略
程序运行时的内存分配策略有三种,分别是静态分配,栈式分配,和堆式分配,对应的,三种存储策略使用的内存空间主要分别是静态存储区(也称方法区)、栈区和堆区
静态存储区(方法区)
主要存放静态数据、全局 static 数据和常量;这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在
栈区
当方法被执行时,方法体内的局部变量(其中包括基础数据类型、对象的引用)都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放;因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限
堆区
又称动态内存分配,通常就是指在程序运行时直接 new 出来的内存,也就是对象的实例;这部分内存在不使用时将会由 垃圾回收器来负责回收
栈与堆的区别
在方法体内定义的(局部变量)一些基本类型的变量和对象的引用变量都是在方法的栈内存中分配的
当在一段方法块中定义一个变量时,Java 就会在栈中为该变量分配内存空间,当超过该变量的作用域后,该变量也就无效了,分配给它的内存空间也将被释放掉,该内存空间可以被重新使用
堆内存用来存放所有由 new 创建的对象(包括该对象其中的所有成员变量)和数组
在堆中分配的内存,将由垃圾回收器来自动管理;在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,这个特殊的变量就是我们上面说的引用变量;我们可以通过这个引用变量来访问堆中的对象或者数组
举个例子
publicclassSample{ ints1 = 0; Sample mSample1 = newSample(); publicvoidmethod(){ ints2 = 1; Sample mSample2 = newSample(); Sample mSample3 = newSample(); }
Sample 类的局部变量 s2 和引用变量 mSample2 都是存在于栈中,但 mSample2 指向的对象是存在于堆上的; mSample3 指向的对象实体存放在堆上,包括这个对象的所有成员变量 s1 和 mSample1,而它自己存在于栈中
结论
- 局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中;因为它们属于方法中的变量,生命周期随方法而结束
- 成员变量全部存储与堆中(包括基本数据类型,引用和引用的对象实体)—— 因为它们属于类,类对象终究是要被 new 出来使用的
写到这里你就不难看出现在作为一名程序员,那么就免不了要和 Linux 产生一定的联系,因此我也建议大家要学习一下 Linux
学 Linux 最好地方式,就是直接去用!直接将自己的开发环境都改成 linux,一开始很蹩脚,很不适应,这很正常。如果你一直感到很舒服,只能说明你一直没有进步。想想我们学了那么多年英语,绝大多数人还是无法掌握英语,看到英语文档就直接自动屏蔽。其原因都是:一直在学,但从来没在用;只学而不用,没有半点用
在这里提供一份 Linux 全套学习手册:可以私信发送“学习” 即可 免费获取
好了,以上就是今天要分享的内容,大家觉得有用的话,可以点赞分享一下;如果文章中有什么问题欢迎大家指正;欢迎在评论区或后台讨论哈~