Understanding C# Memory Management: Weak References, GC Latency Modes, and Unmanaged Resources

Understanding C# Memory Management: Weak References, GC Latency Modes, and Unmanaged Resources
In this article [Show more]

    Understanding C# Memory Management: Weak References, GC Latency Modes, and Unmanaged Resources

    Memory management is a crucial aspect of programming in C#. Understanding how garbage collection (GC) works, particularly in relation to weak references, GC latency modes, and unmanaged resources, can help you write more efficient and reliable code. In this article, we will explore these concepts with simple examples using C# top-level statements introduced in C# 6 and later.

    C# Weak References

    A weak reference allows you to reference an object without preventing it from being collected by the garbage collector. This is useful when you want to keep track of objects that can be reclaimed if memory is needed.

    using System;
    
    class Program
    {
        static void Main()
        {
            // Create a strong reference to an object
            var strongReference = new MyClass();
    
            // Create a weak reference to the same object
            WeakReference<MyClass> weakReference = new WeakReference<MyClass>(strongReference);
    
            // Check if the weak reference is still alive
            if (weakReference.TryGetTarget(out MyClass target))
            {
                Console.WriteLine("Object is still alive.");
            }
            else
            {
                Console.WriteLine("Object has been collected.");
            }
    
            // Remove the strong reference
            strongReference = null;
    
            // Force garbage collection
            GC.Collect();
            GC.WaitForPendingFinalizers();
    
            // Check again if the weak reference is still alive
            if (weakReference.TryGetTarget(out target))
            {
                Console.WriteLine("Object is still alive.");
            }
            else
            {
                Console.WriteLine("Object has been collected.");
            }
        }
    }
    
    class MyClass
    {
        // Example class
    }
    

    In this example, MyClass is first strongly referenced, then weakly referenced. After removing the strong reference and forcing garbage collection, the weak reference will likely find that the object has been collected.

    GC Latency Mode

    Garbage Collection (GC) latency mode affects how frequently garbage collection occurs. The .NET runtime provides several latency modes to balance between performance and responsiveness.

    GC Latency Modes

    • Batch: GC occurs infrequently, optimizing for throughput.
    • Interactive: Balances between throughput and responsiveness.
    • LowLatency: Minimizes the time spent in GC, useful for real-time applications.

    Example: Setting GC Latency Mode

    You can change the GC latency mode using the GCSettings.LatencyMode property:

     

    using System;
    using System.Runtime;
    
    class Program
    {
        static void Main()
        {
            // Set GC latency mode to LowLatency
            GCSettings.LatencyMode = GCLatencyMode.LowLatency;
    
            // Your application code here
    
            // Restore default latency mode
            GCSettings.LatencyMode = GCLatencyMode.Interactive;
        }
    }
    

    In this example, we set the GC latency mode to LowLatency to reduce GC pauses, and then restore it to Interactive.

    GC Committed Bytes

    The term "committed bytes" refers to the amount of memory that has been allocated and is guaranteed to be available for use. This is important for understanding how much memory your application is using.

    Example: Checking GC Committed Bytes

    You can use the GC.GetTotalMemory method to get the amount of memory currently used:

    using System;
    
    class Program
    {
        static void Main()
        {
            // Get total memory committed
            long committedBytes = GC.GetTotalMemory(false);
    
            Console.WriteLine($"Committed Bytes: {committedBytes}");
        }
    }
    

    Here, GC.GetTotalMemory returns the number of bytes currently allocated. The false parameter means we don’t force a collection before measuring.

    C# Unmanaged Resources

    Unmanaged resources are resources not handled by the garbage collector, such as file handles or database connections. You need to explicitly release these resources to avoid leaks.

    Example: Using Unmanaged Resources

    Here’s how you might work with unmanaged resources:

    using System;
    using System.IO;
    
    class Program
    {
        static void Main()
        {
            // Create a file stream (unmanaged resource)
            using (var fileStream = new FileStream("example.txt", FileMode.Create))
            {
                // Write data to the file
                using (var writer = new StreamWriter(fileStream))
                {
                    writer.WriteLine("Hello, world!");
                }
            } // FileStream is automatically disposed here
        }
    }
    

    In this example, the FileStream is an unmanaged resource. Using a using statement ensures that the FileStream is disposed of properly, releasing the unmanaged resource.

    C# Release All Resources

    Properly releasing resources is essential for efficient memory management. For unmanaged resources, you need to ensure they are disposed of. This is typically done using the IDisposable interface and using statements.

    Example: Implementing IDisposable

    Here’s an example of a class that implements IDisposable:

    using System;
    
    class MyResource : IDisposable
    {
        private bool disposed = false;
    
        public void DoWork()
        {
            if (disposed) throw new ObjectDisposedException(nameof(MyResource));
            // Perform work with the resource
        }
    
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    // Dispose managed resources
                }
                // Dispose unmanaged resources
                disposed = true;
            }
        }
    
        ~MyResource()
        {
            Dispose(false);
        }
    }
    
    class Program
    {
        static void Main()
        {
            using (var resource = new MyResource())
            {
                resource.DoWork();
            } // MyResource is automatically disposed here
        }
    }
    

    In this example, MyResource implements IDisposable to handle both managed and unmanaged resources. The Dispose method is called explicitly when the using block ends, ensuring resources are released.

     

     

    Author Information
    • Author: Ehsan Babaei

    Send Comment



    Comments