Effective C#之Cha2:NET Resource Management

来源:互联网 发布:国外新闻网站推荐 知乎 编辑:程序博客网 时间:2024/06/07 03:40

Chapter 2. .NET Resource Management

.Net的资源管理

The simplefact that .NET programs run in a managed environment has a big impact on thekinds of designs that create effective C#. Taking utmost advantage of thatenvironment requires changing your thinking from native environments to the.NET CLR. It means understanding the .NET Garbage Collector. An overview of the.NET memory management environment is necessary to understand the specificrecommendations in this chapter, so let's get on with the overview.

.Net程序运行在一个托管的环境下,这个简单的事实对创建高效C#的设计种类有很大的影响。要想把环境的优势利用到极限,要求你将思考方式从原始的环境转换到.Net CLR来。这意味着要理解.Net的垃圾收集器。对.Net内存管理环境的概览,对于理解该章节内特定的建议非常必要。因此,让我们开始浏览一下整体。

TheGarbage Collector (GC) controls managed memory for you. Unlike nativeenvironments, you are not responsible for memory leaks, dangling pointers,uninitialized pointers, or a host of other memory-management issues. But theGarbage Collector is not magic: You need to clean up after yourself, too. Youare responsible for unmanaged resources such as file handles, databaseconnections, GDI+ objects, COM objects, and other system objects.

垃圾收集器为你控制着托管内存。与原始环境不同,你不需要为内存泄漏、不定的指针、未初始化的指针或者其它内存管理陷阱问题负责。但是垃圾收集器没有魔力:你也需要在背后进行清理。你对非托管资源不用负责,像文件句柄、数据库连接、GDI+对象、COM对象和其它系统对象。

Here's thegood news: Because the GC controls memory, certain design idioms are mucheasier to implement. Circular references, both simple relationships and complexwebs of objects, are much easier. The GC's Mark and Compact algorithmefficiently detects these relationships and removes unreachable webs of objectsin their entirety. The GC determines whether an object is reachable by walkingthe object tree from the application's root object instead of forcing eachobject to keep track of references to it, as in COM. The DataSet class providesan example of how this algorithm simplifies object ownership decisions. ADataSet is a collection of DataTables. Each DataTable is a collection ofDataRows. Each DataRow is a collection of DataItems. Each DataTable alsocontains a collection of DataColumns. DataColumns define the types associatedwith each column of data. There are other references from the DataItems to itsappropriate column. Every DataItem also contains a reference to its container,the DataRow. DataRows contain references back to the DataTable, and everythingcontains a reference back to the containing DataSet.

好消息:因为GC(垃圾收集器)控制着内存,一些设计习惯更容易实现。在简化关系的同时也复杂化了对象页面的循环指针,也变得更容易了。GC的“标记和压缩算法,在它们中检测关系、移除不可达页面对象上,是高效的。GC,由应用程序的根对象开始遍历对象树,而不是强迫每个对象保持一个对它的引用的轨迹(COM中一样),来判定一个对象是否可达。DataSet类提供了一个例子:演示该算法如何简化对对象所有者的判断。DataSetDataTable的集合,每个DataTableDataRow的集合,每个DataDataItem的集合。每个DataTable也包含一个DataColumn的集合,DataColumn定义了和每一栏数据相关联的类型。从DataItem到它相应的栏存在其它的引用。每个DataItem也包含有对容器DataRow的引用,DataRow包含有对DataTable的引用,所有一切都包含一个对DataSet的引用。

If that's not complicated enough, you can create DataViews that provideaccess to filtered sequences of a data table. Those are all managed by aDataViewManager. There are references all through the web of objects that makeup a DataSet. Releasing memory is the GC's responsibility. Because the .NETFramework designers did not need to free these objects, the complicated web ofobject references did not pose a problem. No decision needed to be maderegarding the proper sequence of freeing this web of objects; it's the GC'sjob. The GC's design simplifies the problem of identifying this kind of web of objectsas garbage. After the application releases its reference to the dataset, noneof the subordinate objects can be reached. It does not matter that there arestill circular references to the DataSet, DataTables, and other objects in theweb. Because these objects cannot be reached from the application, they are allgarbage.

如果那还不够复杂的话,你可以创建DataView,提供对数据表格过滤后的序列的访问。它们由DataViewManager管理。遍布所有对象页面的引用,组成了DataSet。释放内存是GC的职责。因为.Net框架设计者没有必要释放这些对象,所以复杂的对象页面引用不会产生问题。因为不用考虑合适的释放对象页面的队列,所以不需做决定,这是GC的工作。GC的设计简化了这个问题:将这种类型的页面对象定义为垃圾。在应用程序释放了对dataset的引用后,没有任何下级对象都能到达。仍然存在对DataSetDataTable或其它处于页面中的对象的循环引用,但是这没什么。因为这些对象不能由应用程序到达,所以是垃圾。

The Garbage Collector runs in its own thread to remove unused memoryfrom your program. It also compacts the managed heap each time it runs.Compacting the heap moves each live object in the managed heap so that the freespace is located in one contiguous block of memory. Figure 2.1 shows twosnapshots of the heap before and after a garbage collection. All free memory isplaced in one contiguous block after each GC operation.

垃圾收集器以自己的线程运行,从程序里移除未使用的内存,每次运行时也压缩托管堆。通过压缩堆,将托管堆里面的活动对象进行移动,可以使得空闲空间集中在一个连续的内存块里。图2.1 展示了堆的在垃圾收集前和垃圾收集后的2个快照。在每次GC进行操作后,所有的自由内存都集中在一个连续的块里面。

Figure 2.1 垃圾收集器不仅移除未使用的内存,而且也移动内存里的其它对象,并对是已经使用的内存进行压缩,从而使得自由空间最大化。

 

As you've just learned, memory management is completely theresponsibility of the Garbage Collector. All other system resources are yourresponsibility. You can guarantee that you free other system resources bydefining a finalizer in your type. Finalizers are called by the system beforean object that is garbage is removed from memory. You can and mus tuse thesemethods to release any unmanaged resources that an object owns. The finalizerfor an object is called at some time after it becomes garbage and before thesystem reclaims its memory. This nondeterministic finalization means that youcannot control the relationship between when you stop using an object and whenits finalizer executes. That is a big change from C++, and it has importantramifications for your designs. Experienced C++ programmers wrote classes thatallocated a critical resource in its constructor and released it in itsdestructor:

正如你刚学到的一样,内存管理完全是垃圾收集器的职责,管理所有其它的系统资源则是你的职责。你可以通过在类型内定义终结器来保证释放其它系统资源。在一个已经成为垃圾的对象从内存中移除之前,终结器会被调用。你能够并且应该使用这些方法来释放对象拥有的任何未托管的资源。一个对象,在它成为垃圾后、系统重新获得它的内存前,它的终结器会在某个时候被调用。这个非确定的终结过程意味着,你不能控制下面2者之间的关系:何时停止使用一个对象,它的终结器何时被执行。从C++来说,这是一个巨大的改变,使你的设计也有了重要的分歧。有经验的C++程序员编写了这样的类:在构造函数里面分配了关键的资源,在析构函数里面进行了释放;

  1. // Good C++, bad C#:
  2. class CriticalSection
  3. {
  4.    public:
  5.       // Constructor acquires the system resource.
  6.       CriticalSection( )
  7.       {
  8.         EnterCriticalSection( );
  9.       }
  10.        // Destructor releases system resource.
  11.       ~CriticalSection( )
  12.       {
  13.         ExitCriticalSection( );
  14.       }
  15. };
  16.      // usage:
  17.     void Func()
  18.     {
  19.         // The lifetime of s controls access to
  20.         // the system resource.
  21.         CriticalSection s;
  22.         // Do work.
  23.         //...
  24.         // compiler generates call to destructor.
  25.         // code exits critical section.
  26.     }

This common C++ idiom ensures thatresource deallocation is exception-proof. This doesn't work in C#, however atleast, not in the same way. Deterministic finalization is not part of the .NETenvironment or the C# language. Trying to force the C++ idiom of deterministicfinalization into the C# language won't work well. In C#, the finalizereventually executes, but it doesn't execute in a timely fashion. In theprevious example, the code eventually exits the critical section, but, in C#,it doesn't exit the critical section when the function exits. That happens atsome unknown time later. You don't know when. You can't know when.

通常的C++习惯保证了资源的重分配是无异常的。这在C#里面是不能工作的,至少不是用同样的方式工作。确定性的终结过程不是.Net环境或者C#语言的一部分。试图强制将已经习惯的C++的确定性终结方式用到C#语言中,不能很好的工作。在C#里面,终结器是逐渐执行的,但是它不是及时执行的。在前面的例子里,代码逐渐的从关键区域退出,但是在C#里面,当方法退出时,它并不退出关键区域。退出关键区域的行为在以后的某个未知时间发生。你不知道是什么时候,你不可能知道。

Relying on finalizers also introducesperformance penalties. Objects that require finalization put a performance dragon the Garbage Collector. When the GC finds that an object is garbage but alsorequires finalization, it cannot remove that item from memory just yet. First,it calls the finalizer. Finalizers are not executed by the same thread thatcollects garbage. Instead, the GC places each object that is ready forfinalization in a queue and spawns yet another thread to execute all thefinalizers. It continues with its business, removing other garbage from memory.On the next GC cycle, those objects that have been finalized are removed frommemory. Figure 2.2 shows three different GC operations and the difference inmemory usage. Notice that the objects that require finalizers stay in memoryfor extra cycles.

依赖于终结器也引来了性能上的损失。要求有终结过程的对象在性能上对垃圾收集器产生了拖累。当GC发现一个对象成了垃圾又要求进行终结时,还不能从内存中移除它。首先,它调用了终结器。执行终结器的线程和收集垃圾的线程不是同一个。相反,GC将每个准备要终结的对象放在一个队列里面,生成另一个线程来执行所有的终结器。GC继续自己的工作:从内存中移除其它垃圾。在下一轮的垃圾收集周期内,这些已经被终结的对象才被从内存里移除。图 2.2 展示了3个不同的GC操作,以及它们在内存上的不同。注意,要求终结器的对象会在内存中多停留一个额外的周期。

2.2  这个序列展示了终结器作用在垃圾收集器上的效果。对象会在内存里停留更长,一个额外的线程需要被生成来执行垃圾收集器。


This might lead you to believethat an object that requires finalization lives in memory for one GC cycle morethan necessary. But I simplified things. It's more complicated than thatbecause of another GC design decision. The .NET Garbage Collector definesgenerations to optimize its work. Generations help the GC identify thelikeliest garbage candidates more quickly. Any object created since the lastgarbage collection operation is a generation 0 object. Any object that hassurvived one GC operation is a generation 1 object. Any object that hassurvived two or more GC operations is a generation 2 object. The purpose ofgenerations is to separate local variables and objects that stay around for thelife of the application. Generation 0 objects are mostly local variables.Member variables and global variables quickly enter generation 1 and eventuallyenter generation 2.

这可能导致你相信,一个要求进行终结的对象在内存里面要比必要的时间多生存一个GC周期。但是我刚才是将事情简化了。实际上要比这复杂的多,这是由另外一个GC设计决定的。.Net垃圾收集器定义Generations(世代)来优化自己的工作。Generations帮助GC更快的定义最可能的垃圾候选者。在执行最近的垃圾收集操作时,任何被创建的对象,都是第0代对象。任何经历了一次GC操作的对象是第1代对象。任何经历了2次或更多GC操作的对象是第2代对象。分代的目的是:分离局部变量和生命期贯穿整个程序的对象。第0代对象绝大多数是局部变量,成员变量和全局变量迅速的成为第1代,逐渐成为第2代。

The GC optimizes its work bylimiting how often it examines first- and second-generation objects. Every GCcycle examines generation 0 objects. Roughly 1 GC out of 10 examines thegeneration 0 and 1 objects. Roughly 1 GC cycle out of 100 examines all objects.Think about finalization and its cost again: An object that requires finalizationmight stay in memory for nine GC cycles more than it would if it did notrequire finalization. If it still has not been finalized, it moves togeneration 2. In generation 2, an object lives for an extra 100 GC cycles untilthe next generation 2 collection.

通过限制检查第1代和第2代对象的频率,GC优化了自己的工作。每个GC周期都检查第0代对象。粗略来看,1/10GC会检查第0代和第1代对象,1/100GC周期检查所有的对象。再次考虑终结器和它的代价:一个对象,要求了终结过程和没有的相比,可能在内存里面多驻留9GC周期。如果它还没有被终结的话,就会被移入到第2代里面。在第2代里面,一个对象会生存额外的100GC周期,直到下一轮对第2代的收集。

To close, remember that a managedenvironment, where the Garbage Collector takes the responsibility for memorymanagement, is a big plus: Memory leaks and a host of other pointer-relatedproblems are no longer your problem. Nonmemory resources force you to createfinalizers to ensure proper cleanup of those nonmemory resources. Finalizerscan have a serious impact on the performance of your program, but you mustwrite them to avoid resource leaks. Implementing and using the IDisposableinterface avoids the performance drain on the Garbage Collector that finalizersintroduce. The next section moves on to the specific items that will help youcreate programs that use this environment more effectively.

作为总结,记住:垃圾收集器对内存管理负责的托管环境是一个很重要的附加物,内存泄露和其它和指针相关的陷阱问题,将不再是你的问题。非内存资源强迫你创建终结器来保证对这些它们进行合适的清理。终结过程对你的程序性能有深刻的影响,但是必须要编写这些来避免资源泄漏。实现、使用IDisposable接口避免了引入终结器的在垃圾收集器上带来的性能负担。下一节将进入详细的条款,会帮助你创建更高效使用该环境的程序。

 

原创粉丝点击