Too Many Objects
One of the most common problems is creating too many objects. Because allocating new memory with the .NET Framework is quite fast, it’s easy to forget that a single line of code could trigger a lot of allocations. The problem occurs when it comes time to collect these objects. Garbage collection involves a performance penalty, and collecting a large number of unnecessary objects exacerbates the problem.
This problem typically occurs when instructions generated from code create a class of objects known as temporary objects to perform their actions. Many .NET classes create temporary objects for their return values, for temporary strings and for associated classes such as enumerators that serve a necessary but short-lived purpose. An application developer can’t simply use any instruction to perform a particular action because that construct might produce undesirable side effects.
As a simple example, consider an exercise to concatenate two strings. It might seem simple to apply the "+" operator to perform this action. However, the "+" operator causes several new string objects to be created every time text is added to the string. Instead, using the System.Text.StringBuilder class often promotes faster string concatenation without creating new objects. This type of problem can be even worse in cases where a single instruction can create many temporary objects, all of which must be garbage-collected when their work is completed.
Object Leaks
Of course, even with the .NET garbage collection strategy, you can still have object leaks. An object leak occurs when a reference is made accidentally, or not removed appropriately, resulting in the object getting written to when the application is done with it. If this object is still in some way connected to a root structure, it won’t be collected, even if the application is done with that object. An example of such a leak is caching an object reference in a static member variable and forgetting to release it after the end of a request. The memory reference will remain until the application completes and the heap is returned to the operating system. This also leads to the issue of inappropriately long-lived objects. Because garbage collection is automatic, it’s easy to forget memory is still managed according to predefined rules. If an object is kept around long enough to be promoted to generation 2 of collection, it might never be collected until the application exits.
Why is this bad? Because the number of objects stored in the heap will likely keep growing while the application is running. This causes two problems. First, more heap memory extends the amount of time required to garbage collect, slowing down the application. Second, memory is not an infinite resource. If the application runs long enough, it will generate an out-of-memory error.
Too Many Object References
If you create an object that refers to many other objects, it can cause a couple of different problems. First, during all collections, it will force the garbage collector to follow all of the pointers between the objects, lengthening the time needed to complete the process. The results are particularly bad if this is a long-lived object structure, because the garbage collector goes through this process for every collection if the object has been modified.
Plus, large objects can cause problems. The .NET Framework keeps large objects (more than 20,000 bytes) in a separate large object heap, so that it can manage them separately from other objects. Large objects are never compacted because shifting 20,000-byte blocks of memory down in the heap would waste too much CPU time. Yet pointers between large objects and other objects can keep large objects alive longer than they need to be. And objects that are close to the 20,000-byte cutoff will still be allocated in the general-purpose heap, and compacting them will incur a significant performance hit.