垃圾回收系列(1):没有GC,世界将会怎样
2010-11-02 22:31
302 查看
最近在公司内部做了一次关于垃圾回收的讲座,我打算用几篇文章把讲座的内容整理出来,供大家参考。在开始之前,我们有必要稍微复习一下内存分配的主要方式,大多数主流语言都支持三种内存分配方式:
1. 静态分配:静态变量和全局变量的分配形式
2. 自动分配:在栈中为局部变量分配内存的方法
3. 动态分配:在堆中动态分配内存空间以存储数据的方式
如何管理堆对象的生命周期,正是我们要探讨的话题。从面向对象的角度来看,每个对象的生命周期应该由自己管理,也就是说,作为一个对象,它知道自己
什么时候被创建,什么时候被销毁。然而事实上却不是这样,因为对象之间有相互引用关系,所以对象往往不知道自己什么时候可以宣告死亡,如果对象释放太早,
会造成“悬空引用”问题;如果释放的太晚或者不释放,又会造成内存泄露问题。
在C/C++中提供了显式的内存管理方案,可以用malloc/new来显式分配一段内存,而当这段内存不再需要的时候,使用free/delete把它返回给系统,看下面这段代码:
这个过程非常的自然清晰,不是吗?在使用之前用new分配一段内存,使用完毕后再用delete销毁。可惜的是,现实世界中的代码不都是这么简单,开发人员会由于各种各样的原因,而忘记释放内存,或者释放内存不正确,从而导致内存泄露。下面是一个典型的内存泄露示例:
显然这里释放内存的工作应该由开发人员完成,但开发人员却忘记了调用free()。
这段代码的问题在于不正确的调用了delete,提前释放了x的内存而导致最后一句ptr->data出错。麻烦的事情远不止这些,继续看下面这段代码:
这段代码中看起来很美,使用new来为100个string对象分配内存,最后也使用了delete来销毁,但不幸的是这段代码仍然不正确,这里为
100个string对象分配的对象,最后可能有99个string未必删除,原因在于没有使用相同形式的new和delete,正确的应该为:
注意到最后那个[]了吗?简单的说,在调用new时使用了[],在调用delete时也要使用[]。但这条规则又不像我们说的这么简单,有了typedef,在调用new时可能没有使用[],但是在调用delete时却要使用[],如下面这段代码:
噩梦还没有到此结束,如果我们有两个类型Array和NamedArray,其中NamedArray公有继承于Array,如下面的代码所示:
开发人员在使用上面两个类型时,写了这样一段代码:
看出问题所在了吗?最后一行调用delete并不能释放aname所占用的内存,原因在于Array类型的析构函数~Array()并没有被声明为virtual!所以父类的析构函数并没有表现出多态的特性。
通过上面的几个例子,想必大家已经体会到了显式管理内存的困难,于是在C++里出现了智能指针的概念,来减少开发人员手工管理内存出错的可能性,最
常见的如STL中的std::auto_ptr,本质上它也是个普通的指针,只不过std::auto_ptr会在析构的时候调用 delete
操作符来自动释放所包含的对象。
在大名鼎鼎的Boost C++库中更是包含了很多的智能指针,可用于各种情况,如作用域指针boost::scoped_ptr,共享指针boost::shared_ptr等等,如下代码所示:
对象生命周期管理,除了使用显式管理方案之后,还有一种机制就是隐式管理,即垃圾回收(Garbage
Collection,简称为GC),最早出现于世界第二元老语言Lisp中,Jean E.
Sammet曾经说过,Lisp语言最长久的贡献之一是一个非语言特征,即代表了系统自动处理内存的方法的术语极其技术——垃圾回收
(GC,Garbage
Collection)。而现在很多平台和语言都支持垃圾回收机制,如JVM、CLR、Python等。在下一篇文章中将会介绍几种经典的垃圾回收算法。
1. 静态分配:静态变量和全局变量的分配形式
2. 自动分配:在栈中为局部变量分配内存的方法
3. 动态分配:在堆中动态分配内存空间以存储数据的方式
如何管理堆对象的生命周期,正是我们要探讨的话题。从面向对象的角度来看,每个对象的生命周期应该由自己管理,也就是说,作为一个对象,它知道自己
什么时候被创建,什么时候被销毁。然而事实上却不是这样,因为对象之间有相互引用关系,所以对象往往不知道自己什么时候可以宣告死亡,如果对象释放太早,
会造成“悬空引用”问题;如果释放的太晚或者不释放,又会造成内存泄露问题。
在C/C++中提供了显式的内存管理方案,可以用malloc/new来显式分配一段内存,而当这段内存不再需要的时候,使用free/delete把它返回给系统,看下面这段代码:
int main() { string *ptr = new string; // do something delete ptr; }
这个过程非常的自然清晰,不是吗?在使用之前用new分配一段内存,使用完毕后再用delete销毁。可惜的是,现实世界中的代码不都是这么简单,开发人员会由于各种各样的原因,而忘记释放内存,或者释放内存不正确,从而导致内存泄露。下面是一个典型的内存泄露示例:
void service(int n, char ** names) { for (int i=0; i< n; i++) { char * buf = (char *) malloc(strlen(names[i])); strncpy(buf,names[i], strlen(names[i])); } }
显然这里释放内存的工作应该由开发人员完成,但开发人员却忘记了调用free()。
void service() { Node* x = new Node("mid-autumn" ); Node* ptr = x; delete x; cout << ptr->data << endl; }
这段代码的问题在于不正确的调用了delete,提前释放了x的内存而导致最后一句ptr->data出错。麻烦的事情远不止这些,继续看下面这段代码:
int main() { string *ptr = new string[100]; // do something delete ptr; }
这段代码中看起来很美,使用new来为100个string对象分配内存,最后也使用了delete来销毁,但不幸的是这段代码仍然不正确,这里为
100个string对象分配的对象,最后可能有99个string未必删除,原因在于没有使用相同形式的new和delete,正确的应该为:
int main() { string *ptr = new string[100]; // do something delete [] ptr; }
注意到最后那个[]了吗?简单的说,在调用new时使用了[],在调用delete时也要使用[]。但这条规则又不像我们说的这么简单,有了typedef,在调用new时可能没有使用[],但是在调用delete时却要使用[],如下面这段代码:
typedef string address[4]; int main() { string *ptr = new address; // do something delete [] ptr; }
噩梦还没有到此结束,如果我们有两个类型Array和NamedArray,其中NamedArray公有继承于Array,如下面的代码所示:
template <class T> class Array { public : Array(int lowBound, int highBound); ~Array(); private : vector<T> data; size_t size; int lBound, int hBound; }; template <class T> class NamedArray : public Array<T> { public : NamedArray(int lowBound, int highBound, const string& name); private : string* aName; };
开发人员在使用上面两个类型时,写了这样一段代码:
int main() { NamedArray<int > *pna = new NamedArray<int >(10,20,"Users" ); Array<int > *pa; pa = pna; // do something delete pa; }
看出问题所在了吗?最后一行调用delete并不能释放aname所占用的内存,原因在于Array类型的析构函数~Array()并没有被声明为virtual!所以父类的析构函数并没有表现出多态的特性。
通过上面的几个例子,想必大家已经体会到了显式管理内存的困难,于是在C++里出现了智能指针的概念,来减少开发人员手工管理内存出错的可能性,最
常见的如STL中的std::auto_ptr,本质上它也是个普通的指针,只不过std::auto_ptr会在析构的时候调用 delete
操作符来自动释放所包含的对象。
int main() { auto_ptr<int > ptr(new int (42)); cout << *ptr << endl; // 不再需要delete了 }
在大名鼎鼎的Boost C++库中更是包含了很多的智能指针,可用于各种情况,如作用域指针boost::scoped_ptr,共享指针boost::shared_ptr等等,如下代码所示:
#include <boost/shared_ptr.hpp> #include <vector> int main() { std::vector<boost::shared_ptr<int > > v; v.push_back(boost::shared_ptr<int >(new int (1))); v.push_back(boost::shared_ptr<int >(new int (2))); }
对象生命周期管理,除了使用显式管理方案之后,还有一种机制就是隐式管理,即垃圾回收(Garbage
Collection,简称为GC),最早出现于世界第二元老语言Lisp中,Jean E.
Sammet曾经说过,Lisp语言最长久的贡献之一是一个非语言特征,即代表了系统自动处理内存的方法的术语极其技术——垃圾回收
(GC,Garbage
Collection)。而现在很多平台和语言都支持垃圾回收机制,如JVM、CLR、Python等。在下一篇文章中将会介绍几种经典的垃圾回收算法。
相关文章推荐
- 假如没有百度,世界将会怎样?
- 如果没有电话,世界将会怎样
- 如果没有电话,世界将会怎样
- Java十年 没有Java世界将会怎样?
- 没有Google的世界会是怎样
- 垃圾回收系列(4):GC性能调优及总结
- 如果地球没有重力将会怎样? 万物将会灭亡
- 中国没有Google,国民将会怎样?
- 没有乘法口诀表将会怎样:古巴比伦乘法和古埃及乘法
- 残缺的苹果完美的世界 没有乔布斯世界会怎样?
- ddd的战术篇: 如果没有domain object 世界会是怎样的?
- 怎样才能赚到钱系列(十一):拥抱变化的世界
- 1000年后的世界将会怎样
- 假如全球所有计算机硬盘中的数据丢失,世界将会怎样?
- 如果没有 Android,世界会怎样?
- 如果没有嵌入式 这个世界将会是怎样
- 如果没有 Android,世界会怎样?-IT蓝豹
- 假如P=NP,世界将会怎样?
- 没有Google的世界会是怎样
- 如果没有typeof,内核将会怎样?