两种内存管理方法的比较
时间:2010-06-13 来源:sinodragon21
在为C++开发垃圾回收器之前,比较垃圾回收和内建于C++中的手工方法是有好处的。通常,在C++中使用动态内存需要两个步骤。首先,通过new 从堆中分配内存。然后在不需要这块内存的时候,使用delete释放它。因此,每一次动态分配都要遵循下面的顺序:
p = new some_object;
//...
delete p;
通常,每一次使用new分配内存后,都必须有匹配的delete操作来释放内存。如果不使用delete,内存就不会被释放,即使您的程序已经不再 需要这块内存。
垃圾回收在一个关键方式上不同于手工方法:它自动释放不再需要的内存。因此,通过使用垃圾回收,动态分配只需要一步操作。例如,在Java和C# 中,使用new分配需要的内存,但是在程序中绝对不需要显式地释放它。相反,垃圾回收器会定期运行,查找不再有其他对象指向的内存块。当没有其他对象指向 一个动态内存块时,就意味着程序的元素不再使用这块内存。当找到一块不再使用的内存时,垃圾回收器就会释放它。因此,在一个垃圾回收系统中,没有 delete运算符,也不需要。
乍看上去,垃圾回收的内在简单性使得它成为管理动态内存显而易见的选择。事实上,人们可能会有疑问,究竟为什么要使用手工方法,特别是对于C++这 样一种成熟的语言。然而,在动态分配的时候,第一感觉是具有欺骗性的,因为这两种方法都涉及到一组权衡。哪一种方法更好是由应用程序决定的。下面部分描述 了一些涉及到的问题。
2.1.1 手工内存管理的优缺点
手工管理动态内存的主要优点是效率。由于没有使用垃圾回收器,从而不需要花费时间来跟踪活动的对象或者周期性地查找不再使用的内存。而是当程序员知 道分配的对象不再需要这块内存的时候,他可以显式地释放它,而不需要多余的开销。由于没有垃圾回收相关的开销,手工方法可以编写更加高效的代码。这就是 C++需要支持手工内存管理的原因之一:它能够建立高性能代码。
手工管理的另一个优点是控制。尽管要求程序员同时处理内存的分配和释放是一个负担,但这样做的好处是程序员获得了对这个过程两个方面的完全控制。您 精确地知道分配内存的时刻,也精确地知道释放它的时刻。另外,当通过delete释放一个对象的时候,其析构函数在这个时刻执行,而不是像垃圾回收那样在 后面的某个时候执行。因此,通过手工方法,可以精确地控制指定对象销毁的时刻。
尽管手工内存管理的效率高,但是它也容易导致相当恼人的一类错误:内存泄漏。由于必须手工释放内存,可能(甚至很容易)忘记这样做。忘记释放不再使 用的内存意味着这块内存仍然被分配,即使不再需要它。但在垃圾回收环境中不会发生内存泄漏,因为垃圾回收器确保不再使用的对象最终会被释放。在 Windows程序设计中,内存泄漏尤其恼人,在这个系统中忘记释放不再使用的内存会逐渐地降低系统性能。
C++的手工方法可能涉及到的其他问题包括:过早地释放了仍然在使用的内存,或者不小心将同一块内存释放了两次。这两种错误都会导致严重的问题。而 且它们不会立即显示任何征兆,这就使得很难发现这类错误。
2.1.2 垃圾回收的优缺点
实现垃圾回收有多种方法,每一种方法都提供了不同的性能特征。然而,所有的垃圾回收系统都具有一个共同的、与手工方法相对的属性。垃圾回收最主要的 优点是简单和安全。在垃圾回收环境中,可以显式地使用new分配内存,但是不需要显式地释放内存。相反,不再使用的内存会被自动回收。因此,不可能会忘记 释放对象或者过早地释放对象。这样做简化了程序设计,并且阻止了有问题的类。另外,不可能会意外地两次释放动态分配的内存。因此,垃圾回收为内存管理问题 提供了一种易于使用的、不容易犯错的、可靠的解决方案。
遗憾的是,垃圾回收的简单及安全性是有代价的。第一个代价是垃圾回收机制引起的开销。所有垃圾回收的配置都会消耗一些CPU资源,因为这种不再使用 的内存的回收并不是一个免费过程。当使用手工方法的时候,不会有这样的开销。
第二个代价是在销毁对象时容易失控。使用手工方法时,当对对象执行delete语句的时候,及时地销毁这个对象(和所调用的它的析构函数),而垃圾 回收没有这种切实而快速的规则。相反,当使用垃圾回收时,直到回收器运行并回收对象的时候,对象才会被销毁,而回收器只有在某个特定时刻才会运行。例如, 回收器只有在自由内存的数量低于某个值的时候才会运行。另外,用户并不能总是知道垃圾回收器销毁对象的顺序和时间。在某些情况下,不能准确地知道对象销毁 的时间会导致一些问题,因为这意味着程序不能准确地知道何时为动态分配的对象调用析构函数。
对于作为后台任务运行的垃圾回收系统,这种失控可能会引发某种应用程序潜在的更加严重的问题,因为这样做将某种本质上不确定的行为引入到程序中。在 后台运行的垃圾回收器实际上在不可预知的某个时刻回收不再使用的内存。例如,回收器通常只有在CPU空闲的时候才会运行。由于可能从一个程序的运行转到下 一个程序,从一台计算机转到下一台计算机,或者从一个操作系统转到另一个操作系统,因此垃圾回收器在程序中执行的确切位置是不能确定的。对于许多应用程序 而言,这并不存在问题,但是对于实时应用程序这可能会引发灾难,因为在实时应用程序中对垃圾回收器不可预知的CPU循环的分配会导致事件的丢失。
2.1.3 两种方法都可以使用
正如前面讨论所阐述的那样,手工管理和垃圾回收都强化了一个特性而牺牲了另一个特性。手工方法强化了效率和控制,牺牲了安全性和易用性。垃圾回收强 化了简单性及安全性,但是付出了运行性能降低和控制丢失的代价。因此,垃圾回收及手工内存管理本质上是相对的,每一种方法都强化了另一种方法牺牲的特性。 这就是没有一种动态内存管理的方法可以适用于所有的程序设计情况的原因。
尽管这两种方法是对立的,但是它们并不互相排斥,它们可以共存。因此对于C++程序员,这两种方法都可以使用,只需为手头的任务选择一种合适的方 法。所要做的事情只是为C++建立一个垃圾回收器,这就是本章下面部分的主题。