Effective C#之Cha2:NET Resource Management

rel="File-List" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_filelist.xml"> rel="Edit-Time-Data" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_editdata.mso"> rel="themeData" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_themedata.thmx"> rel="colorSchemeMapping" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_colorschememapping.xml">

Chapter 2. .NET Resource Management

.Net的资源管理

The simple fact that .NET programs run in a managed environment has a big impact on the kinds of designs that create effective C#. Taking utmost advantage of that environment 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 specific recommendations in this chapter, so let's get on with the overview.

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

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

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

Here's the good news: Because the GC controls memory, certain design idioms are much easier to implement. Circular references, both simple relationships and complex webs of objects, are much easier. The GC's Mark and Compact algorithm efficiently detects these relationships and removes unreachable webs of objects in their entirety. The GC determines whether an object is reachable by walking the object tree from the application's root object instead of forcing each object to keep track of references to it, as in COM. The DataSet class provides an example of how this algorithm simplifies object ownership decisions. A DataSet is a collection of DataTables. Each DataTable is a collection of DataRows. Each DataRow is a collection of DataItems. Each DataTable also contains a collection of DataColumns. DataColumns define the types associated with each column of data. There are other references from the DataItems to its appropriate column. Every DataItem also contains a reference to its container, the DataRow. DataRows contain references back to the DataTable, and everything contains 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 provide access to filtered sequences of a data table. Those are all managed by a DataViewManager. There are references all through the web of objects that make up a DataSet. Releasing memory is the GC's responsibility. Because the .NET Framework designers did not need to free these objects, the complicated web of object references did not pose a problem. No decision needed to be made regarding the proper sequence of freeing this web of objects; it's the GC's job. The GC's design simplifies the problem of identifying this kind of web of objects as garbage. After the application releases its reference to the dataset, none of the subordinate objects can be reached. It does not matter that there are still circular references to the DataSet, DataTables, and other objects in the web. Because these objects cannot be reached from the application, they are all garbage.

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

The Garbage Collector runs in its own thread to remove unused memory from 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 free space is located in one contiguous block of memory. Figure 2.1 shows two snapshots of the heap before and after a garbage collection. All free memory is placed in one contiguous block after each GC operation.

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

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

 

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

正如你刚学到的一样,内存管理完全是垃圾收集器的职责,管理所有其它的系统资源则是你的职责。你可以通过在类型内定义终结器来保证释放其它系统资源。在一个已经成为垃圾的对象从内存中移除之前,终结器会被调用。你能够并且应该使用这些方法来释放对象拥有的任何未托管的资源。一个对象,在它成为垃圾后、系统重新获得它的内存前,它的终结器会在某个时候被调用。这个非确定的终结过程意味着,你不能控制下面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 that resource deallocation is exception-proof. This doesn't work in C#, however at least, not in the same way. Deterministic finalization is not part of the .NET environment or the C# language. Trying to force the C++ idiom of deterministic finalization into the C# language won't work well. In C#, the finalizer eventually executes, but it doesn't execute in a timely fashion. In the previous example, the code eventually exits the critical section, but, in C#, it doesn't exit the critical section when the function exits. That happens at some 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 introduces performance penalties. Objects that require finalization put a performance drag on the Garbage Collector. When the GC finds that an object is garbage but also requires finalization, it cannot remove that item from memory just yet. First, it calls the finalizer. Finalizers are not executed by the same thread that collects garbage. Instead, the GC places each object that is ready for finalization in a queue and spawns yet another thread to execute all the finalizers. It continues with its business, removing other garbage from memory. On the next GC cycle, those objects that have been finalized are removed from memory. Figure 2.2 shows three different GC operations and the difference in memory usage. Notice that the objects that require finalizers stay in memory for extra cycles.

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

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


This might lead you to believe that an object that requires finalization lives in memory for one GC cycle more than necessary. But I simplified things. It's more complicated than that because of another GC design decision. The .NET Garbage Collector defines generations to optimize its work. Generations help the GC identify the likeliest garbage candidates more quickly. Any object created since the last garbage collection operation is a generation 0 object. Any object that has survived one GC operation is a generation 1 object. Any object that has survived two or more GC operations is a generation 2 object. The purpose of generations is to separate local variables and objects that stay around for the life of the application. Generation 0 objects are mostly local variables. Member variables and global variables quickly enter generation 1 and eventually enter generation 2.

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

The GC optimizes its work by limiting how often it examines first- and second-generation objects. Every GC cycle examines generation 0 objects. Roughly 1 GC out of 10 examines the generation 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 finalization might stay in memory for nine GC cycles more than it would if it did not require finalization. If it still has not been finalized, it moves to generation 2. In generation 2, an object lives for an extra 100 GC cycles until the 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 managed environment, where the Garbage Collector takes the responsibility for memory management, is a big plus: Memory leaks and a host of other pointer-related problems are no longer your problem. Nonmemory resources force you to create finalizers to ensure proper cleanup of those nonmemory resources. Finalizers can have a serious impact on the performance of your program, but you must write them to avoid resource leaks. Implementing and using the IDisposable interface avoids the performance drain on the Garbage Collector that finalizers introduce. The next section moves on to the specific items that will help you create programs that use this environment more effectively.

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值