0

I am new to C# and am currently learning about the Garbage Collector. Specifically I am now learning about the WeakReference class.

The example below is from MSDN's documentation on WeakReference.

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        // Create the cache.
        int cacheSize = 50;
        Random r = new Random();
        Cache c = new Cache(cacheSize);

        string DataName = "";
        GC.Collect(0);

        // Randomly access objects in the cache.
        for (int i = 0; i < c.Count; i++) {
            int index = r.Next(c.Count);

            // Access the object by getting a property value.
            DataName = c[index].Name;
        }
        // Show results.
        double regenPercent = c.RegenerationCount/(double)c.Count;
        Console.WriteLine($"Cache size: {c.Count}, Regenerated: {regenPercent:P0}");
    }
}

public class Cache
{
    // Dictionary to contain the cache.
    static Dictionary<int, WeakReference> _cache;

    // Track the number of times an object is regenerated.
    int regenCount = 0;

    public Cache(int count)
    {
        _cache = new Dictionary<int, WeakReference>();

        // Add objects with a short weak reference to the cache.
       for (int i = 0; i < count; i++) {
            _cache.Add(i, new WeakReference(new Data(i), false));
        }
    }

    // Number of items in the cache.
    public int Count
    {
        get {  return _cache.Count; }
    }

    // Number of times an object needs to be regenerated.
    public int RegenerationCount
    {
        get { return regenCount; }
    }

    // Retrieve a data object from the cache.
    public Data this[int index]
    {
        get {
            Data d = _cache[index].Target as Data;
            if (d == null) {
                // If the object was reclaimed, generate a new one.
                Console.WriteLine("Regenerate object at {0}: Yes", index);
                d = new Data(index);
                _cache[index].Target = d;
                regenCount++;
            }
            else {
                // Object was obtained with the weak reference.
                Console.WriteLine("Regenerate object at {0}: No", index);
            }

            return d;
       }
    }
}

// This class creates byte arrays to simulate data.
public class Data
{
    private byte[] _data;
    private string _name;

    public Data(int size)
    {
        _data = new byte[size * 1024];
        _name = size.ToString();
    }

    // Simple property.
    public string Name
    {
        get { return _name; }
    }
}
// Example of the last lines of the output:
//
// ...
// Regenerate object at 36: Yes
// Regenerate object at 8: Yes
// Regenerate object at 21: Yes
// Regenerate object at 4: Yes
// Regenerate object at 38: No
// Regenerate object at 7: Yes
// Regenerate object at 2: Yes
// Regenerate object at 43: Yes
// Regenerate object at 38: No
// Cache size: 50, Regenerated: 94%

How does the Garbage Collector prioritize which WeakReference objects it is going to collect? Why did the GC choose to remove 94% of the objects from the cache and keep only 6%?

Daniel Schilling
  • 4,829
  • 28
  • 60
Salo7ty
  • 475
  • 5
  • 18

1 Answers1

2

WeakReferences allow the references object to be garbage collected. And the garbage collector will collect all collectable objects in the generation it is collecting.

My guess is that some of the object has been promoted to gen 1/2 when the call to GC.Collect(0) occur. If you replace it with GC.Collect(2) I would expect no objects to be left alive.

WeakReference is not typically a great idea for caching. I avoid it, and use strong reference when I want to cache something. Usually in combination with some way to estimate memory usage to be able to set a upper memory limit. Memory is often plentiful nowdays, so reducing memory usage is of limited importance.

There is also the related concept of memory pooling, this can be used to reduce gen 2 GCs when using large memory buffers.

Edit:

Simplified your example a bit:

    static void Main(string[] args)
    {
        for (int i = 0; i < 100; i++)
        {
            GC.TryStartNoGCRegion(10000000);
            var cache = new Cache(50);
            Console.Write("Objects alive Before: " + cache.CountObjectsAlive());
            GC.EndNoGCRegion();
            GC.Collect(2, GCCollectionMode.Forced);
            Console.WriteLine("\tAfter : " + cache.CountObjectsAlive());
        }
        Console.ReadKey();
    }

public class Cache
{
    // Dictionary to contain the cache.
    Dictionary<int, WeakReference> _cache = new Dictionary<int, WeakReference>();

    public Cache(int count)
    {
        // Add objects with a short weak reference to the cache.
        for (int i = 0; i < count; i++)
        {
            _cache.Add(i, new WeakReference(new byte[i * 1024], false));
        }
    }
    public int CountObjectsAlive() => _cache.Values.Count(v => v.Target != null);
}

This consistently gives 50 objects alive before GC and 0 objects alive after. Note the usage of TryStartNoGCRegion to prevent GC from running while creating the objects. If I change the program to ensure some objects are promoted to gen 1/2 and only collect gen 0, I get some surviving objects.

So I would say my point still stands. GC will collect all objects in the generation it collects. And you are probably better of not messing with WeakReferences unless you have some specific use case.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • (first thank you to reply) no , it still there are objects alive when i set GC.Collect(2) , but i noticed thing , if i remove GC.Collect() , all objects will alive but when cach size <=50 , when i increase cach size above 50 , for example 100 , output is Regenerated: 66.00 % although i did not tell the gc to collect using GC.Collect() , what is this mean ? – Salo7ty Jul 02 '20 at 17:11
  • 1
    @Saki7ty The GC will run whenever the runtime feels like it. So it is expected that it will run if you allocate more memory. – JonasH Jul 03 '20 at 08:58
  • thank you very much , but i have a question when runtime call GC when there are high memory usage why it promote some objects to gen 1/2 and dont remove all objects , on the other hand when i call Gc.Collect() it removes all objects and dont promote any object to next Generation , are there difference between runtime garbage collection and Gc.Collect() ?? – Salo7ty Jul 03 '20 at 14:51
  • 1
    @salo7ty There should not be a difference if GC.Collect or runtime collection. My best guess is that there is something keeping a reference when the GC is run. For example, a GC could run while the Data-constructor is running, at that time the data object would be alive, and it would therefore be promoted. – JonasH Jul 03 '20 at 15:14