垃圾自动回收
我们可以先看下不支持垃圾回收语言的资源管理方式,以下为一小段C++的代码:
void foo()
{
char* p = new char[128];
...//对p的内存块进行赋值
func1(p);//使用内存指针
delete[] p;
}
各种非预期的原因,比如由于开发者的疏忽导致最后的delete语句没有被调用,都会引发经典而恼人的内存泄漏问题。假如该函数被调用的非常频繁,那么我们观察该进程执行时,会发现,该进程所占用的内存会一直疯长,甚至会占用所有系统内存并导致程序崩溃,而如果泄漏的是系统资源的话,那么后果还会更加严重,最终很有可能导致系统崩溃。
手动管理内存的另外一个问题就是由于指针的导出传递而无法确认何时可以释放该指针指向的内存块。假如代码中某个位置释放了内存,而另一处还在使用指向这块内存的指针,那么这些指针就变成了所谓的“野指针”(wild pointer)或者“悬空指针”(dangling pointer),对这些指针进行的任何读写操作都会导致不可预期的后果。
由于杰出的效率,C和C++语言在非常长的时间内都作为服务端系统的主要开发语言,比如Apache,Nginx和Mysql等著名的服务端软件就是用C和C++开发的。然而,内存和资源管理一直是一个令人非常抓狂的问题。服务器的崩溃十有八九就是不正确的内存和资源管理导致的,更讨厌的是这种内存和资源管理问题即使被发现了,也很难定位到具体的错误地点,导致附属程序员通宵达旦的调试程序。
这个问题在多年里被不同的人用不同的方式来试图解决,并诞生了一些非常著名的内存检查工具,比如Rational Pirify,Compuware BoundsChecker 和英特尔的Oarallel Inspector等。从设计角度也诞生了类似于内存引用计数之类的方法(通常被称为智能指针),后续在windows平台上标准化的COM出现的一个重要原因就是解决内存管理的难题。但是事实证明,这些工具和方法能够在一定程度上辅助开发者,但并没法让开发者避免通宵调试这样又苦又累的工作。
到目前为止,内存泄漏最佳的解决方法是在语言级别引入自动垃圾回收算法(Garbage Collection, 简称GC)。所谓垃圾回收,即所有的内存分配工作都会被在运行时记录,同时任何对该内存的使用也会被记录,然后垃圾回收器会对所有已经分配的内存进行跟踪监测,一旦发现有些内存不再被任何人使用,就阶段性的回收这些没人用的内存,当然,因为需要尽量最小话垃圾回收的性能损耗,以及降低对正常程序执行过程的影响,现实中的垃圾回收算法要比这个复杂的多,比如为对象增加年龄属性等,但基本原理都是如此。
自动垃圾回收在C/C++社区一直作为一柄双刃剑看待,虽然到C++0x(后命名为C++11)正式发布时,这个呼声频高的特性总算是有人发起提案,但按C++之父的说法,由于C++本身过于强大,导致C++中支持垃圾收集变成了一个困难的工作,这也使得垃圾回收最终与C++11无缘。假如C++支持垃圾收集,以下的代码片段在运行时就会是个严峻的考验:
int* p = new int;
p+=10; //对指针进行了偏移,因此那块内存不在被引用
//.... 这里可能会发生指针对这块int内存的垃圾回收
p-=10; //指针又回到了原来的位置
*p=10;// 如果有垃圾收集,这里就无法保证可以正常运行了
微软的C++/CLI算是用一种偏门的方式让C++程序猿有机会品尝了一下垃圾回收功能的鲜美味道。在C/C++后出现的语言,比如java,C#等,基本都已经自带垃圾回收的功能了
golang最为一门新生的开发语言,当然不能忽略内存管理这个问题。又因为golang没有C++那么强大的指针计算功能,因此可以很自然的包含垃圾回收功能。因为垃圾回收功能的支持,开发者无需担心所指向的对象失效的问题,因此golang中不需要delete关键字,也不需要free()方法来明确释放内存。
对于以上C语言的例子,如果使用golang实现,我们就完全不用考虑合适需要释放之前分配的内存问题,系统会自动帮我们判断,并在合适的时候(比如CPU相对空闲的时候)进行自动垃圾收集工作。