.NET框架自动内存管理(1)
对于C/C++程序员,在内存管理方面慎之又慎一点也不过分,因为内存漏损(占用的内存未及时释放)和野指针(指向已释放的内存块)错误给应用程序稳定性带来的影响可能会是灾难性的,而你又很难预料这一错误何时发生以及错误的后果。
到了.NET框架时代,C#程序员在内存管理方面的担忧可以一扫而去了。因为.NET框架的垃圾回收器自动管理着应用程序的内存分配和释放。垃圾回收器优化引擎根据正在进行的内存分配情况确定执行内存回收的最佳时间。当垃圾回收器执行内存回收时,它检查托管堆中不再被应用程序使用的对象并执行必要的操作来回收它们占用的内存。
对于应用程序创建的大多数托管对象,可以依靠.NET框架的垃圾回收器隐式地执行所有必要的内存管理任务。但是,对于封装了非托管资源的对象,当应用程序使用完这些非托管资源之后,必须显式地释放它们。最常见的一类非托管资源就是封装了操作系统资源的对象,例如文件、窗口或网络连接。虽然垃圾回收器可以跟踪封装非托管资源的对象的生存期,但它不了解具体如何清理这些资源。
1.内存管理问题
我们先看一下非托管的C++代码是如何发生内存错误的。考查以下C++示例代码:
class Caller {
......
public:
CallerMethod() {
......
Foo* pFoo = new Foo();
pFoo->FooMethod();
delete pFoo;
......
}
......
}
// unmanaged C++ class
class Foo {
private:
// a reference to a String object
String objString;
public:
// constructor
Foo() {
objString = null
}
// destructor
~Foo() {
if (objString != null) {
delete objString;
}
}
public:
void FooMethod() {
objString = new String();
}
}
应用程序通过new操作符为C++对象Foo申请到内存后,该内存将一直被占用直到Foo对象的析构函数被调用时才回收。因此C++对象的析构函数同时也是最后检查并释放其内部成员可能申请的内存的合适的地方。而C++对象析构函数的调用是由delete操作符触发的,如果应用程序没有显式的通过delete操作符通知操作系统调用C++对象的析构函数,析构函数将永远不会被调用,C++对象占用的内存也就没有被释放的机会,这时就发生了内存漏损。而当C++对象的析构函数被调用之后,这时该对象的实例将指向一自由内存块,继续使用该实例引用将导致意想不到的后果。
如果应用程序都能如上述示例那样完美的保证每个对象引用的new和delete操作符(准确的说应是内存申请的new和delete操作符)匹配,而且保证在对引用变量进行delete操作后不再使用该引用变量访问对象实例,则C++程序根本不会发生内存错误!然而实际的应用程序要比示例复杂得多,程序员并不总是能够保证,或者需要花费相当大的精力才能够保证做到上述两点,特别是对于一个欠缺经验的程序员更是如此。如果他不小心违反了上述两个保证中的任何一个,那么他的恶梦就开始了,操作系统对他爱莫能助。