一、在操作系统中,系统会给每个进程分配虚拟地址,虚拟地址的大小与处理器的位数有关,如32位处理器进程可分配4GB的虚拟内存供程序正常运行。
这4GB的虚拟内存,存储单元从地址0开始进行排序,此地址为虚拟地址。
此虚拟地址可分为:
1、栈区(stack):由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。线程也有自己维护的栈。
2、堆区(heap) :程序动态申请的空间,由程序释放或其他方式释放,若没有释放,可能导致内存泄露。
3、全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、文字常量区:常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区:存放函数体的二进制代码。
二、下面主要分析栈区和堆区的分配和释放方式:
1、栈区:栈区是从高地址往低地址分配的,初始的栈区栈指针指向栈的最高地址单元,而释放总是从低地址往高地址释放(先进后出)。栈区的分配与释放由编译器自动完成。
如下:
{
int a;
//do something
{
double b ;
//do something
}
}
整形a将先入栈高地址空间,占4个字节,接着双精度浮点数b入栈,占8个字节。变量b的作用域比a先结束,于是b所占据的空间先被回收。
如下图所示,红色区域为已占用区,黄色区域为空闲区
带引用的情况:
{
Customer a;
a = new Customer();
}
此种情况a将在栈区分得4个单元空间以存放Customer实例的地址。
Customer对象将存储在堆上。
2、堆区:
堆区与栈区的地址结构类似,不同点在于:
a、需要程序主动申请,释放根据不同的语言释放方式不同(c、c++、delphi等需要程序显示释放,.net有自动回收机制)
b、堆区是由低地址向高地址分配的。释放的顺序是不定的,基于堆上对象的生存周期。会产生内存碎片。
三、内存的释放
1、写delphi程序时我们通常强调内存哪里申请哪里释放,因为delphi的程序是程序代码负责释放内存,又称为纯手工释放方式。
这种方式的有点是内存能够得到及时释放,而缺点是由于手工,我们可能常忘记写释放代码,导致内存泄露出现错误,而这种错误通常又很难跟踪定位泄露的地方。
在实际工作中已遇到过不少此类情况,排错的过程很是头痛,解决办法就是不要忘记释放对象。
2、在ms中COM采用的是对象维护引用技术。COM对象自己维护一个计数,记录自己被引用的次数,当这个计数变为0时,也就是不会再用时,COM自己释放掉内存。
3、.net的运行库采用的是垃圾回收器来回收的方式。垃圾回收器本身也是一个程序。程序动态请求的内存都分配到堆上,在.net中,CLR维护它自己的托管堆供.net程序使用。
每隔一段时间.net就会检查托管堆,当检查到需要清理堆时,.net就调用垃圾回收器这个程序。垃圾回收器会扫描堆上的对象的引用,不再有引用的对象就被删除。
垃圾回收器调用的时间是不确定的,除非代码中有调用垃圾回收器(System.GC.Collect())。关于.net非托管堆内存释放将在下一篇中讲解。