Chapter 22. Memory Management

One of the greatest advantages to using a managed language such as C# in the Common Language Runtime (CLR) environment is that you don’t have to worry about memory management as much as you did in languages such as C or C++.

That said, there are still memory-related topics that you need to understand as a C# developer. Furthermore, sometimes you just have to drop out of the world of managed code and get access to pointers. It’s ugly, and you should try to avoid it, but it’s there when you need it.

Measure Memory Usage of Your Application

Solution: The GC class contains many handy memory-related methods, including GetTotalMemory(), which is the amount of memory the garbage collector thinks is allocated to your application. The number may not be exactly right because of objects that haven’t been garbage collected yet. However, this has the advantage of being able to tell you about how much memory a certain part of your program uses, rather than the entire process.

image

This same code prints the following:

Before allocations: 651,064
After allocations: 40,690,080

Get the OS View of Your Application’s Memory

You can also ask the operating system to give you information about your process:

image

Here’s the output:

image

What each of those numbers mean is not necessarily intuitive, and you should consult a good operating system book, such as Windows Internals (Microsoft Press), to learn more about how virtual memory works.

Note

You can also use performance counters to track this and a lot of other information about your application or its environment. You can access these interactively from perfmon.exe, or see the MSDN documentation for the PerformanceCounter class to see how you can incorporate these into your own applications.

Clean Up Unmanaged Resources Using Finalization

If for some reason you want to manage raw file handles, bitmaps, memory-mapped files, synchronization objects, or any other kernel object, you need to ensure that the resource is cleaned up. Each of these resources is represented by handles. To manipulate handles, you need to use native Windows functions via P/Invoke (see Chapter 26, “Interacting with the OS and Hardware”).

Solution: Here’s an example of a class that manages a file handle with help from the .NET classes:

image

image

image

When .NET detects a finalizer in a class, it ensures that it is called at some point in garbage collection.

Note

The finalization phase of the collection process can be quite expensive, so it’s important to use finalizers only when necessary. They should almost never be used for managed resources—only unmanaged ones.

Clean Up Managed Resources Using the Dispose Pattern

Many types of resources are limited (files, database connections, bitmaps, and so on) but have .NET classes to manage them. Like unmanaged resources, you still need to control their lifetimes, but because they are wrapped in managed code, finalizers are not appropriate.

Solution: You need to use the dispose pattern. Let’s look at a similar example as the last section, but with a managed resource. The IDisposable interface defines a Dispose method, but to use the pattern correctly you need to define a few more things yourself:

image

image

When using the dispose pattern, you call Dispose yourself when you’re done with the resource:

image

This pattern is so common that there is a shortcut syntax:

image

Note

You should not use the Dispose pattern with Windows Communication Framework objects. Unfortunately, they are not implemented according to this pattern. You can find more information on this problem by doing an Internet search for the numerous articles out there detailing this problem.

Use Dispose with Finalization

Whereas finalizers should not touch managed objects, Dispose can (and should) clean up unmanaged resources. Here’s an example:

image

image

image

image

image

Here’s a little program to demonstrate what happens:

image

It gives the following output:

image

See the Dispose project in the included source code.

Force a Garbage Collection

Solution: The short answer is, don’t. You are unlikely to outguess the garbage-collection system for efficiency. That said, if you really know what you’re doing and you’re going to measure the effects, here’s how to do it:

GC.Collect();

One reason why people force a collection is that they’re beginning a performance-sensitive operation that they don’t want interrupted by garbage collection. However, the garbage collection system in .NET 4 has undergone major revision from previous versions and now does more collections in the background, so the need to do forced collections should be lessened.

Create a Cache That Still Allows Garbage Collection

Solution: The problem is that just holding a reference to an object prevents it from being garbage collected. Enter WeakReference.

This simple Cache class uses WeakReference objects to store the actual values, allowing them to be garbage collected as needed.

image

image

Note

Weak references should never be used for small items, and you should not rely on them as a crutch to solve memory problems for you. They are best used for items that can use a lot of memory, but are easily recreated as needed, such as in cache situations where it would be nice if the object were still around in memory, but you still want it to be garbage collected eventually.

This test program shows how it could be used:

image

image

image

Use Pointers

Solution: To use pointers in your code, you have to do a few things:

1. Mark the code block as unsafe.

2. Compile the project as unsafe using the /unsafe compiler switch (or in your project build settings).

3. Ensure that the target runtime environment has sufficient privilege to run unsafe code (it is a security risk after all).

Here’s a simple example:

image

You can create pointers to any non-reference type (that also contains only non-reference types):

image

Entire methods and classes can be marked as unsafe:

unsafe class MyUnsafeClass {...}
unsafe void MyUnsafeMethod() {...}

Speed Up Array Access

Solution: By using pointers, you can speed up array lookups by an order of magnitude, but at the price of code safety and guarantees.

This code shows that by using pointers you can gain direct access to memory, potentially overwriting data you didn’t mean to:

image

You can also use pointer arithmetic, just as you would in native languages:

image

Note that adding one to a pointer does not increase the memory address by 1 byte, but by 1 increment of the data type size, which in this example is an int, or 4 bytes.

Note

Most programs do not need to use any of this pointer stuff, and it is quite dangerous to do so, as evidenced by the “unsafe” status you have to grant the code, in addition to the increased permissions required to run programs that do this. If at all possible, try to create programs that do not rely on these techniques.

Prevent Memory from Being Moved

When the garbage collector runs, it moves objects around in memory in a compaction process. This requires fixing up all the memory addresses for all the moved objects. Obviously, this is not possible for objects the GC does not know about—such as in a native code interop situation.

Solution: “Fix” the memory in place.

image

Note

You should keep memory fixed only as long as you absolutely need to. While memory is fixed in place, the GC is less efficient as it attempts to work around the unmovable block.

Allocate Unmanaged Memory

Solution: Use the Marshal.AllocHGlobal() method and manually add some memory pressure:

image

image

When you allocate memory in this way, .NET knows nothing about it, as this program snippet shows:

image

The output is as follows:

Memory usage before unmanaged allocation: 665,456
Memory usage after unmanaged allocation: 706,416

The AddMemoryPressure method notifies the CLR that it should take this into account when scheduling garbage collection, but that is all the involvement it has. When your unmanaged memory is freed, you should release this pressure.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset