Memory management

When talking about memory management, any code programmer will remember how native languages opened their doors to any kind of issues and bottlenecks. This can also mean that the expert C++ programmer could have access to some customization to produce better memory management than CLR does. However, this relates only to very few people in very few cases.

Theoretically speaking, when a programmer needs to use some memory to store any value in an operation, they need to:

  • Define a variable of the chosen type
  • Allocate enough free memory to contain the variable:
    • Reserve some bytes in the operating system's memory stack to contain the variable
  • Use the variable:
    • Instantiate the variable with the needed value
    • Do whatever is needed with such variable, for example - Define variable, allocate memory, use your variable, deallocate memory
  • De-allocate the freed memory:
    • Once the variable becomes useless, free the related memory for further usage by this or other applications

Other than the usual generic programming issues with this step sequence, such as using the wrong type, wasting memory, or going against an overflow of the type, the trickiest memory management issues are memory leak and memory corruption:

  • Memory leak: This occurs anytime we forget to de-allocate memory, or by letting the application always consume more memory, and offer an easy-to-predict result.
  • Memory corruption: This occurs when we free memory by de-allocating some variable, but somewhere in our code, we still use this memory (because it is referred by another variable as a pointer), unaware of such de-allocation. This happens because when we de-allocate variables and relative memory, we must always be sure of updating (or de-allocating) all eventually related pointers that otherwise may still point to a freed memory area that could also contain other data.

CLR helps us by managing memory itself. Thus, in the .NET world, the previous list becomes the following:

  • Declaring a variable of any type
  • Instantiating the variable with a valid value:
    • CLR makes the difference between value-types and reference-types regarding initial values of variables before assignation. Reference-types have an initial value of null (Nothing in VB). Value-types (all primitive-types except string and object), instead, are always valued at the default value. Value-types may support a null value through the class Nullable<T> or by adding the character ?at the type ending, like int? (only in C#).
  • Using the variable

It is obvious here that memory management is done completely by the CLR, which allocates the needed variable memory plus some overhead (a pointer to a type instance and a sync block index) as soon as the variable is instantiated. A target-system sized integer pointer that points to an instance of type class represents the type of the variable and the another value of the same size used for synchronizing the variable usage. This means that on any 32-bit system, any variable will add 2 x 32-bit values, while for 64-bit systems, a variable will add 2 x 64-bit values. This explains the small additional memory usage that occurs on 64-bit systems. All those objects are arranged in sequence in a memory area called the managed heap.

Note

C#/VB variable value assignation is a bit different. C# uses early binding, with a built-in type-safe validation for constant and (often) variable values. A down-casting (in terms of numeric type capability) must pass through a cast operation such as int a = (int)longValue;. When a value outside of the smaller type ranges enters the cast, -1 becomes the new value. VB, instead, uses late binding that accepts any value assignation (with built-in support for conversions and parsing), by default. Because of the lazy approach, in VB, a numeric conversion must be compliant to the new type's value ranges. Here, a cast operation does not occur, so an eventual OverflowException is the result of a code like this: Dim a As Integer = longValue.

The CLR also manages another internal memory area called the managed stack. Each thread handles its own stack (this is why often we refer it as the thread stack) by storing all value-types values in a Last-In-First-Out (LIFO) manner. The purpose of CLR is to abstract memory allocation; thus, directly trying to impact that the kind of memory used is actually some kind of inference in CLR itself. To be honest, it's possible to use explicitly stack memory by switching to C# in unmanaged coding (with a proper keyword, such as unsafe) using C++ related techniques, or using only value-types such as integers, double, chars, and so on in managed C#. When using managed C#, the stack memory is available only until we program in a procedural way. This happens because any type within an object (a reference-type) will be stored in the managed heap. Although storing data in the stack will boost the value read/write speed in the memory, it is like programming in the 1960s.

An interesting read is an article by Eric Lippert, the Chief Programmer of C# compiler team at Microsoft. Find it at http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx.

The heap is a growing list of bytes that contains a First-In-First-Out (FIFO) collection. It is always slightly bigger than needed, as it quickly accepts new values, exactly the same as any .NET List<T> collection. The CLR also has a pointer or cursor that is always pointing to the newly available space for any future allocation.

Here is a diagram showing such FIFO-like memory handling with the new-item cursor:

Memory management

The heap memory allocation model

This heap population job occurs on a portion of memory that is assigned to the application by the CLR, the address space, in which the Windows environment is actually a Virtual Address Space because it can span from physical memory to page files. This whole application's memory space is then divided into regions—small memory portions side by side to assemble table pages for the compiled CIL, plus metadata and other regions that are eventually created as more memory requests occur.

Memory management

Memory availability in Microsoft Windows systems and CLR

Although for Windows-based systems the theoretic virtual memory available for application address space is 8 TB (64-bit) or 1.5 GB (32-bit), always remember that the address space may be fragmented. This will easily reduce real address space availability for simple variables like huge collections. This is why a CLR running at 32-bit usually raises an OutOfMemoryException error at around 1 GB of memory consumption if we simply populate a huge List<T>.

The difficult part of the job of CLR is freeing such a heap: instead of an unmanaged language in which this job is assigned to the programmer, here, the CLR de-allocates the memory just when the variable exits the scope (if it lives in a managed stack) or when there is no more reference by any other object and it exits the scope (if it lives in a managed heap). This job occurs in a lazy fashion with an internal heuristic that also looks for memory requests. This is why, on a system with high address space available, an application that consumes 100 MB of memory can simply continue consuming the same amount of memory, although it is not used anymore if the application does nothing. However, as soon as possible, when the application needs to create new objects, it could trigger the memory cleanup of the heap by starting an operation named garbage collection.

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

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