Automatic memory management is one of the services that the common language runtime provides during Managed Execution.
The common language runtime's garbage collector manages the allocation
and release of memory for an application. For developers, this means
that you do not have to write code to perform memory management tasks
when you develop managed applications. Automatic memory management can
eliminate common problems, such as forgetting to free an object and
causing a memory leak, or attempting to access memory for an object that
has already been freed. This section describes how the garbage
collector allocates and releases memory.
When you initialize a new process, the runtime reserves a
contiguous region of address space for the process. This reserved
address space is called the managed heap. The managed heap maintains a
pointer to the address where the next object in the heap will be
allocated. Initially, this pointer is set to the managed heap's base
address. All reference types
are allocated on the managed heap. When an application creates the
first reference type, memory is allocated for the type at the base
address of the managed heap. When the application creates the next
object, the garbage collector allocates memory for it in the address
space immediately following the first object. As long as address space
is available, the garbage collector continues to allocate space for new
objects in this manner.
Allocating memory from the managed heap is faster than
unmanaged memory allocation. Because the runtime allocates memory for an
object by adding a value to a pointer, it is almost as fast as
allocating memory from the stack. In addition, because new objects that
are allocated consecutively are stored contiguously in the managed heap,
an application can access the objects very quickly.
The garbage collector's optimizing engine determines the
best time to perform a collection based on the allocations being made.
When the garbage collector performs a collection, it releases the memory
for objects that are no longer being used by the application. It
determines which objects are no longer being used by examining the
application's roots. Every application has a set of roots. Each root
either refers to an object on the managed heap or is set to null. An
application's roots include global and static object pointers, local
variables and reference object parameters on a thread's stack, and CPU
registers. The garbage collector has access to the list of active roots
that the just-in-time (JIT) compiler
and the runtime maintain. Using this list, it examines an application's
roots, and in the process creates a graph that contains all the objects
that are reachable from the roots.
Objects that are not in the graph are unreachable from
the application's roots. The garbage collector considers unreachable
objects garbage and will release the memory allocated for them. During a
collection, the garbage collector examines the managed heap, looking
for the blocks of address space occupied by unreachable objects. As it
discovers each unreachable object, it uses a memory-copying function to
compact the reachable objects in memory, freeing up the blocks of
address spaces allocated to unreachable objects. Once the memory for the
reachable objects has been compacted, the garbage collector makes the
necessary pointer corrections so that the application's roots point to
the objects in their new locations. It also positions the managed heap's
pointer after the last reachable object. Note that memory is compacted
only if a collection discovers a significant number of unreachable
objects. If all the objects in the managed heap survive a collection,
then there is no need for memory compaction.
To improve performance, the runtime allocates memory for
large objects in a separate heap. The garbage collector automatically
releases the memory for large objects. However, to avoid moving large
objects in memory, this memory is not compacted.
To optimize the performance of the garbage collector, the
managed heap is divided into three generations: 0, 1, and 2. The
runtime's garbage collection algorithm is based on several
generalizations that the computer software industry has discovered to be
true by experimenting with garbage collection schemes. First, it is
faster to compact the memory for a portion of the managed heap than for
the entire managed heap.
Secondly, newer objects will have shorter
lifetimes and older objects will have longer lifetimes. Lastly, newer
objects tend to be related to each other and accessed by the application
around the same time.
The runtime's garbage collector stores new objects in
generation 0. Objects created early in the application's lifetime that
survive collections are promoted and stored in generations 1 and 2. The
process of object promotion is described later in this topic. Because it
is faster to compact a portion of the managed heap than the entire
heap, this scheme allows the garbage collector to release the memory in a
specific generation rather than release the memory for the entire
managed heap each time it performs a collection.
In reality, the garbage collector performs a collection
when generation 0 is full. If an application attempts to create a new
object when generation 0 is full, the garbage collector discovers that
there is no address space remaining in generation 0 to allocate for the
object. The garbage collector performs a collection in an attempt to
free address space in generation 0 for the object. The garbage collector
starts by examining the objects in generation 0 rather than all objects
in the managed heap. This is the most efficient approach, because new
objects tend to have short lifetimes, and it is expected that many of
the objects in generation 0 will no longer be in use by the application
when a collection is performed. In addition, a collection of generation 0
alone often reclaims enough memory to allow the application to continue
creating new objects.
After the garbage collector performs a collection of
generation 0, it compacts the memory for the reachable objects as
explained in Releasing Memory
earlier in this topic. The garbage collector then promotes these
objects and considers this portion of the managed heap generation 1.
Because objects that survive collections tend to have longer lifetimes,
it makes sense to promote them to a higher generation. As a result, the
garbage collector does not have to reexamine the objects in generations 1
and 2 each time it performs a collection of generation 0.
After the garbage collector performs its first collection
of generation 0 and promotes the reachable objects to generation 1, it
considers the remainder of the managed heap generation 0. It continues
to allocate memory for new objects in generation 0 until generation 0 is
full and it is necessary to perform another collection. At this point,
the garbage collector's optimizing engine determines whether it is
necessary to examine the objects in older generations. For example, if a
collection of generation 0 does not reclaim enough memory for the
application to successfully complete its attempt to create a new object,
the garbage collector can perform a collection of generation 1, then
generation 2. If this does not reclaim enough memory, the garbage
collector can perform a collection of generations 2, 1, and 0. After
each collection, the garbage collector compacts the reachable objects in
generation 0 and promotes them to generation 1. Objects in generation 1
that survive collections are promoted to generation 2. Because the
garbage collector supports only three generations, objects in generation
2 that survive a collection remain in generation 2 until they are
determined to be unreachable in a future collection.
For the majority of the objects that your application
creates, you can rely on the garbage collector to automatically perform
the necessary memory management tasks. However, unmanaged resources
require explicit cleanup. The most common type of unmanaged resource is
an object that wraps an operating system resource, such as a file
handle, window handle, or network connection. Although the garbage
collector is able to track the lifetime of a managed object that
encapsulates an unmanaged resource, it does not have specific knowledge
about how to clean up the resource. When you create an object that
encapsulates an unmanaged resource, it is recommended that you provide
the necessary code to clean up the unmanaged resource in a public Dispose method. By providing a Dispose
method, you enable users of your object to explicitly free its memory
when they are finished with the object. When you use an object that
encapsulates an unmanaged resource, you should be aware of Dispose
and call it as necessary. For more information about cleaning up
unmanaged resources and an example of a design pattern for implementing Dispose, see Garbage Collection.
|
4/10/2013
Automatic Memory Management
Subscribe to:
Post Comments
(
Atom
)
No comments :
Post a Comment