Finalizers


Unmanaged Resources

Objects that incorporate unmanaged resources have a managed and unmanaged context, which affects memory management and garbage collection.

For example, the unmanaged resource of a managed object might consume copious amount of unmanaged memory. This cannot be ignored. Managed and unmanaged memories are allocated from the same pool of virtual memory. Corrective action must be taken when the pool is drained, regardless of the source. Developers can compensate for unmanaged resources in .NET Framework 2.0 and take corrective action to prevent out of memory occurrences. You can no longer hide an unmanaged elephant behind a managed object. A managed wrapper class for a bitmap is one example. The wrapper class is relatively small, and the memory associated with the managed object is trivial. However, the bitmap is the elephant. A bitmap requires large amounts of memory, which cannot be ignored. If ignored, creating several managed bitmaps could unexpectedly cause your application to be trampled by a stampede of elephants.

The availability of an unmanaged resource can be discrete. When consumed in a managed application, discrete resources must be tracked to prevent overconsumption. The overconsumption of unmanaged resources can have an adverse affect on the managed application, including the potential of resource contention or raising an exception. For example, a device can support three simultaneous connections. What happens when the fourth connection is requested? In the managed environment, you should be able to handle this scenario gracefully.

The lifetime of an unmanaged resource is a separate consideration apart from the lifetime of the managed object that hosts that resource. Equating the lifetimes can delay the release of sensitive resources. This delay can result in resource contention, poor user responsiveness, or application failure. For example, the FileStream class is a wrapper for a native resource: a physical file. The FileStream instance, which is a managed component, is collected nondeterministically. Therefore, the lapse between when the file is no longer needed and the managed component is collected could be substantial. This could prevent access to the file from within and outside your application. When the file is no longer required, the ability to release the file deterministically is imperative. You need the ability to say "Close the file now." The Disposable pattern provides this ability to managed developers and separates the lifetime of managed components and unmanaged resources.

Garbage Collection Overview

This section is an overview of garbage collection. A detailed explanation of the mechanics of garbage collection is presented in Chapter 13, "Advanced Debugging," and not repeated here. Two assumptions guide the implementation of garbage collection in the managed environment. The first assumption is that objects are more likely to communicate with other objects of a similar size. The second assumption is that smaller objects are short-lived objects, whereas larger objects are long-lived objects. For these reasons, the GC attempts to organize objects based on size and age.

In the managed environment, garbage collection is nondeterministic. With the exception of the GC.Collect method, which is discouraged, developers cannot explicitly initiate garbage collection. The managed heap is partitioned into generations, which are Generations 0, 1, and 2. The initial sizes of the generations are about 256 kilobytes, 2 megabytes, and 10 megabytes, respectively. As the application executes, the GC fine-tunes the thresholds based on the pattern of memory allocations. Garbage collection is prompted when the threshold for Generation 0 is exceeded. At that time, nonrooted objects are removed from memory. Objects that survive garbage collection are promoted from Generation 0 to 1. Generation 0 is now empty. The generations are then compacted and references updated. If during garbage collection Generation 0 and 1 exceed their thresholds, both are collected. Surviving objects are promoted from Generation 0 to 1 and from Generation 1 to 2. Afterward, Generation 0 is empty again, generations are compacted, and references are updated. In fewer instances, all three generations will exceed thresholds and require collection. The later generations contain larger objects, which live longer. Because the short-lived objects reside primarily on Generation 0, most garbage collection is focused in this generation. Generations allow the garbage collection to implement a partial cleanup of the managed heap at a substantial performance benefit.

Objects larger than 85 kilobytes are considered large objects and are treated differently from other objects. Large objects are placed on the large object heap, not on a generation. Large objects are generally long-lived components. Placing large objects on the large object heap eliminates the need to promote these objects between generations, thereby conserving resources and reducing the number of overall collection cycles. The large object heap is collected with Generation 2, so large objects are collected only during a full garbage-collection cycle.

When garbage collection is performed, the GC builds an object graph to determine which objects are not rooted and can be discarded. The object graph is a graph of live objects. First the GC populates the object graph with root references. The root references of an application are global, static, and local references. Local references include references that are locals and function parameters. The GC then adds to the graph those objects reachable from a root reference. An embedded object of a local is an example of an object reachable from a root reference. Of course, the embedded object could contain other objects. The GC extrapolates all the reachable objects to compose the branches of the object graph. Objects can appear once in the object graph, which avoids circular references and other problems. Objects not in the graph are not rooted and are considered candidates for garbage collection.

Objects that are not rooted can hold outstanding references to other objects. In this circumstance, the entire branch is disconnected from a rooted object and can be collected. (See Figure 14-1.)

image from book
Figure 14-1: Object graph with nonrooted objects

The Rooted application demonstrates how nonrooted objects are collected. In the application, the Branch class contains the Leaf class, which contains the Leaf2 class. The Branch class is used as a field in the Form1 class. When an instance of the Branch class is created, it is rooted through the Form class. Leaf and Leaf2 instances are rooted through the Branch instances. (See Figure 14-2.)

image from book
Figure 14-2: Object graph of the Rooted application

In the following code, a modal message box is displayed in the finalizer. This is done for demonstration purposes only and generally considered bad practice.

 public class Branch {     ~Branch()     {         MessageBox.Show("Branch d'tor");     }     public Leaf e= new Leaf(); } public class Leaf {     ~Leaf() {         MessageBox.Show("Leaf d'tor");     }     public Leaf2 e2= new Leaf2(); } public class Leaf2 {     ~Leaf2()     {         MessageBox.Show("Leaf2 d'tor");     } } 

The user interface of the Rooted application allows users to create the Branch, which includes Leaf instances. (See Figure 14-3.) You can then set the Branch or any specific Leaf to null, which interrupts the path to the root object. That nullified object is collectable. Objects that are rooted through the nullified object are also now collectable. For example, if the Leaf instance is set to null, the Branch instance is not collectable; it is before the Leaf reference in the object graph. However, Leaf2 object is immediately a candidate for collection. After setting the Branch or Leaf objects to null, garbage collection can occur. If it doesn't occur, click the Collect Memory button to force garbage collection.

image from book
Figure 14-3: Rooted application

As mentioned, garbage collection occurs when the memory threshold of Generation 0 is exceeded. Other events, which are described in the following list, can prompt garbage collection:

  • When the threshold of Generation 0 is exceeded, garbage collection is triggered. Certain activities, such as frequent allocations, can accelerate garbage collection cycles.

  • Garbage collection is conducted when the extents of memory pressure are reached. The GC.AddMemoryPressure method applies pressure to the managed heap for an unmanaged resource.

  • The limit of an unmanaged resource is reached. HandleCollector sets limits for unmanaged resources.

  • Garbage collection can be forced with the GC.Collect method. This behavior is not recommended because forcing garbage collection is expensive. Nonetheless, this is sometimes necessary.

  • Garbage collection also occurs when overall system memory is low.

Certain suppositions are made for garbage collection in the managed environment. For example, small objects are generally short-lived. Coding contrary to these assumptions can be costly. Although it makes for an interesting theoretical experiment, this is not recommended for production applications. Defining a basket of short-lived but larger objects is an example of coding against assumptions of managed garbage collection, which would force frequent and full collections. Full collections are particularly expensive. Defining a collection of near-large objects—objects that are slightly less than 85 kilobytes—is another example. These objects would apply immediate memory pressure. Because they are probably long-lived, you have the overhead of eventually promoting the near-large objects to Generation 2. It would be more efficient to pad the objects with a buffer, forcing them into large object status, in which the objects are directly placed onto the large object heap. You must remain cognizant of the underlying principal of garbage collection. Implement policies that enhance, not exasperate, garbage collection in the managed environment.

One such policy is to limit boxing. Constant boxing of value types can cause garbage collection more often. Boxing creates a copy of the value type on the managed heap. Most value types are small. For this reason, the resulting object placed on the managed heap could be larger than the original value. This is yet another reason that boxing is inefficient and should be avoided when possible. This is particularly a problem with collection types. The best solution is to use generic types.

This introduction to garbage collection omits the topic of finalization, which is discussed in complete detail later in this chapter.

GC Flavors

There are two flavors of the garbage collection: Workstation GC is optimized for a workstation or single-processor system, whereas Server GC is fine-tuned for a server machine that has multiple processors. Workstation GC is the default; Server GC is never the default—even with a multiprocessor system. Server GC can be enabled in the application configuration file. If your application is hosted by a server application, that application might select the Server GC.

Workstation GC can have concurrent garbage collection enabled or disabled. Concurrent is a single thread concurrently servicing the user interface and garbage collection. The thread simultaneously responds to events of the user interface while also conducting garbage collection responsibilities. The alternative is noncurrent. When garbage collection is performed, other responsibilities, such as servicing the user interface, are deferred.

Workstation GC with Concurrent Garbage Collection

Workstation GC with concurrent garbage collection is the default. It is ideal for desktop applications, in which the user interface must remain responsive. When garbage collection occurs, it will not subjugate the user interface. Concurrent applies only to full garbage collection, which is the collection of Generation 2. When Generation 2 is collected, Generation 0 and 1 are also collected. Partial garbage collection of Generation 0 or 1 is an expeditious activity. The potential impact on the user interface is minimal, and concurrent garbage collection is not merited.

The following are the garbage collection steps for Workstation GC with concurrent processing:

  1. Garbage collection is triggered.

  2. All managed threads are paused.

  3. Perform garbage collection. If garbage collection is completed, proceed to step 5.

  4. Interrupt garbage collection. Resume threads for a short time to respond to user interface requests. Return to step 3.

  5. Resume threads.

Threads are suspended at a secure point. The CLR maintains a table of secure points for use during garbage collection. If a thread is suspended at an nonsecure point, the thread is hijacked until a secure point is reached or the current function returns.

Workstation GC Without Concurrent Garbage Collection

Workstation GC without concurrent garbage collection is selected when Server GC is chosen on a single-processor machine. With this option, priority is placed on garbage collection in lieu of the user interface.

Here are the garbage collection steps for Workstation GC without concurrent processing:

  1. Garbage collection is triggered.

  2. All managed threads are paused.

  3. Garbage collection is conducted.

  4. Managed threads resume.

Server GC

Server GC is designed for multiprocessor machines that are commonly deployed as servers, such as Web, application, and database servers. In the server environment, emphasis is on throughput and not the user interfaces. The Server GC is fine-tuned for scalability. For optimum scalability, garbage collection is not handled on a single thread. The Server GC allocates a separate managed heap and thread for every processor. Garbage collection is not conducted on the thread that requests the allocation.

Here are the garbage collection steps with Server GC:

  1. A GC thread performs an allocation.

  2. Garbage collection is triggered.

  3. Managed threads are suspended.

  4. Garbage collection is performed.

  5. Managed threads are resumed.

Configuring GC Flavor

Workstation GC with concurrent garbage collection can be enabled or disabled in an application configuration file. Because concurrent is the default, the configuration file is used primarily to disable concurrent garbage collection. This is set in the gcConcurrent tag, as shown in the following code:

 <configuration>     <runtime>         <gcConcurrent enabled="false"/>     </runtime> </configuration> 

Server GC is also stipulated in the application configuration file. Remember, Server GC is never the default—even in a multiprocessor environment. Select the Server GC using the gcServer tag, which is exhibited in the following code:

 <configuration>     <runtime>         <gcServer enabled="true"/>     </runtime> </configuration> 

You can also use the .NET Framework Configuration Tool (mscorcfg.msc) to select the appropriate GC. This tool is installed in the Administrative Tools of the Control Panel. Open the .NET Framework Configuration tool and select the Applications icon, which is presented in the left pane. Click the Add An Application To Configure link in the Applications window. From the Configure An Application dialog box, browse and select the target application. The application window will appear. In that window, click the View The Application's Properties link. The application's Properties dialog box will appear. From the two options, you can select the appropriate Garbage Collection mode in this window. Figure 14-4 shows the application properties dialog box and the garbage collection options.

image from book
Figure 14-4: Application properties in the .NET Framework Configuration tool




Programming Microsoft Visual C# 2005(c) The Language
Microsoft Visual Basic 2005 BASICS
ISBN: 0619267208
EAN: 2147483647
Year: 2007
Pages: 161

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net