C# Garbage Collector - узнать число ссылок на объект и другая полезная информация от GC

CG держит число ссылок на объект для удаления данного объекта, но как узнать число ссылок или ещё лучше узнать контекст хранения (где был создан, откуда ссылается)?
Google привёл только к WeakReference, что полезно для отслеживания состояния Alive, но не предоставляет никакой информации о числе ссылок.

Конечная цель - расширенная отладка, хранить таблицу (в идеале граф) созданных объектов разного типа, время создания, время уничтожения (не обязательно), число ссылок, реальный адрес в памяти, фрагментация кучи (с какого по какой адрес ОЗУ используются фрагменты кучи). А так-же выводить этот граф (рисовать через Graphics или отдавать по json - уже отдельная тема). Не все подряд ссылки на определённый тип объекта, как это делают встроенные средства диагностики, а конкретные объекты добавленные в отслеживание из самого кода программы.

Как реализовать данную задачу или по-минимому узнать хотя бы от GC число ссылок на объект? Есть для этих целей в фреймворки необходимые классы? Или отдельные библиотеки/фреймворки?


Ответы (1 шт):

Автор решения: Blackmeser

Источник: https://blog.maartenballiauw.be/post/2017/01/03/exploring-.net-managed-heap-with-clrmd.html
ДеУказателеОбъектизатор: https://stackoverflow.com/a/53029501/1955772
Протестировано в C# Interractive 3.9.0-6.21124.20 (Visual Studio 16.9.1) с Microsoft.Diagnostics.Runtime 2.0.226801

Создание экземпляров:

class MyClass
{
    public int a { get; set; } = 0;
}
List<MyClass> listA = new List<MyClass>();
listA.Add(new MyClass() { a = 1 });
listA.Add(new MyClass() { a = 2 });
List<MyClass> listB = new List<MyClass>();
listB.Add(new MyClass() { a = 3 });
listB.Add(new MyClass() { a = 4 });
MyClass objectC = new MyClass() { a = 5 };
listA.Add(objectC);
listB.Add(objectC);
List<MyClass> listC = new List<MyClass>();
listC.Add(objectC);
List<MyClass> listD = new List<MyClass>();
listD.Add(objectC);
static List<MyClass> controlList = new List<MyClass>();
controlList.Add(objectC); //objectC = 6 refs
listA.Remove(objectC); //5
objectC = null; //4
GC.Collect();

//Подключаем либы

using System.Runtime.InteropServices;
#r ".nuget\packages\microsoft.diagnostics.runtime\2.0.226801\lib\netstandard2.0\Microsoft.Diagnostics.Runtime.dll"
using Microsoft.Diagnostics.Runtime;

//Подключаемся к процессу

var dataTarget = DataTarget.AttachToProcess(Process.GetCurrentProcess().Id, false);

// Dump CLR info

var clrVersion = dataTarget.ClrVersions.First();
var dacInfo = clrVersion.DacInfo;
Console.WriteLine("# CLR Info");
Console.WriteLine("Version:   {0}", clrVersion.Version);
Console.WriteLine("Filesize:  {0:X}", dacInfo.IndexFileSize);
Console.WriteLine("Timestamp: {0:X}", dacInfo.IndexTimeStamp);
Console.WriteLine("Dac file:  {0}", dacInfo.PlatformSpecificFileName);

//Подключаем сборку

var runtime = clrVersion.CreateRuntime();
var appDomain = runtime.AppDomains.First();
Console.WriteLine("# Runtime Info");
Console.WriteLine("AppDomain:      {0}", appDomain.Name);
Console.WriteLine("Address:        {0}", appDomain.Address);
Console.WriteLine("Configuration:  {0}", appDomain.ConfigurationFile);
Console.WriteLine("Directory:      {0}", appDomain.ApplicationBase);

// Dump thread info

Console.WriteLine("## Threads");
Console.WriteLine("Thread count:   {0}", runtime.Threads.Length);
Console.WriteLine("");
foreach (var thread in runtime.Threads)
{
    Console.WriteLine("### Thread {0}", thread.OSThreadId);
    Console.WriteLine("Thread type: {0}", 
                            thread.IsBackground ? "Background" : "Foreground");
    Console.WriteLine("");
    Console.WriteLine("Stack trace:");
    foreach (var stackFrame in thread.EnumerateStackTrace())
    {
        Console.WriteLine("* {0}", stackFrame.FrameName);
    }
}

//Подключаем Heap

var heap = runtime.Heap;

//Создаём средство извлечения объекта по указателю

public static class AddressHelper
{
    private static object mutualObject;
    private static ObjectReinterpreter reinterpreter;

    static AddressHelper()
    {
        AddressHelper.mutualObject = new object();
        AddressHelper.reinterpreter = new ObjectReinterpreter();
        AddressHelper.reinterpreter.AsObject = new ObjectWrapper();
    }

    public static IntPtr GetAddress(object obj)
    {
        lock (AddressHelper.mutualObject)
        {
            AddressHelper.reinterpreter.AsObject.Object = obj;
            IntPtr address = AddressHelper.reinterpreter.AsIntPtr.Value;
            AddressHelper.reinterpreter.AsObject.Object = null;
            return address;
        }
    }

    public static T GetInstance<T>(IntPtr address)
    {
        lock (AddressHelper.mutualObject)
        {
            AddressHelper.reinterpreter.AsIntPtr.Value = address;
            return (T)AddressHelper.reinterpreter.AsObject.Object;
        }
    }

    // I bet you thought C# was type-safe.
    [StructLayout(LayoutKind.Explicit)]
    private struct ObjectReinterpreter
    {
        [FieldOffset(0)] public ObjectWrapper AsObject;
        [FieldOffset(0)] public IntPtrWrapper AsIntPtr;
    }

    private class ObjectWrapper
    {
        public object Object;
    }

    private class IntPtrWrapper
    {
        public IntPtr Value;
    }
}

//Считаем число ссылок и опционально извлекаем объект

var numberOfSameTypeObjects = 0;
var uniqueSameTypeObjects = new Dictionary<ulong, int>();
HashSet<ClrType> clrnames = new HashSet<ClrType>();

if (heap.CanWalkHeap)
{
    foreach (var ptr in heap.EnumerateObjects())
    {
        var type = heap.GetObjectType(ptr);
        clrnames.Add(type);
        // Skip if not a string
        if (type == null || !type.Name.Contains(nameof(MyClass)))
        {
            continue;
        }

        // Count total
        numberOfSameTypeObjects++;

        string fixedName = type.Name.Substring(type.Name.LastIndexOf('+') + 1);

        if (fixedName != nameof(MyClass))
        {
            WriteLine($"Error: {type.Name}");
            continue;
        }
        else
        {
            WriteLine($"Found: {type.Name}");
        }

        // Get value
        ulong prt_a = ptr.Address;
        MyClass myClassObject = AddressHelper.GetInstance<MyClass>(new IntPtr((long)prt_a));
        if (myClassObject != null)
        {
            WriteLine($"MyClass.a={myClassObject.a}");
        }
        else
        {
            WriteLine($"MyClass convert error");
        }

        if (uniqueSameTypeObjects.ContainsKey(prt_a))
        {
            uniqueSameTypeObjects[prt_a]++;
        }
        else
        {
            uniqueSameTypeObjects[prt_a] = 1;
        }
    }

    Console.WriteLine("## Object info");
    Console.WriteLine("Object count:     {0}", numberOfSameTypeObjects);
    Console.WriteLine("");

    Console.WriteLine("Most duplicated Objects: (top 5)");
    foreach (var keyValuePair in uniqueSameTypeObjects.OrderByDescending(kvp => kvp.Value).Take(5))
    {
        Console.WriteLine("* {0} usages: {1}", keyValuePair.Value, keyValuePair.Key);
    }
}
else
{
    WriteLine("Not CanWalkHeap");
}

Всё работало со строками, с своими классами - CLRType не знает тип из кода...
error CS0019: Оператор "!=" невозможно применить к операнду типа "ClrType" и "Type". PS В ClrType.FullName = "Submission#70+MyClass", как вернуть нормальное имя? Upd переделал, добавив костыль с приведением типа класса к тексту, вроде получилось, вывод:

Error: System.Collections.Generic.List<Submission#70+MyClass>
Error: Submission#70+MyClass[]
Found: Submission#70+MyClass
MyClass.a=1
Error: Submission#70+MyClass[]
Found: Submission#70+MyClass
MyClass.a=2
Error: System.Collections.Generic.List<Submission#70+MyClass>
Found: Submission#70+MyClass
MyClass.a=3
Error: Submission#70+MyClass[]
Found: Submission#70+MyClass
MyClass.a=4
Found: Submission#70+MyClass
MyClass.a=5
Error: System.Collections.Generic.List<Submission#70+MyClass>
Error: Submission#70+MyClass[]
Error: System.Collections.Generic.List<Submission#70+MyClass>
Error: Submission#70+MyClass[]
Error: System.Collections.Generic.List<Submission#70+MyClass>
Error: Submission#70+MyClass[]
Error: System.Collections.Generic.ObjectEqualityComparer<Submission#70+MyClass>
## Object info
Object count:     17

Most duplicated Objects: (top 5)
* 1 usages: 74857424
* 1 usages: 74857712
* 1 usages: 75045496
* 1 usages: 75045576
* 1 usages: 75045808

Данная попытка - ещё далека до цели, но по крайней мере что-то может.
Большой минус - нельзя сравнить тип напрямую, как это можно исправить?

→ Ссылка