Viewing garbage collection history in c# (VS2015)
See the question and my original answer on StackOverflowWhat you could do is use Microsoft's CLR MD, a runtime process and crash dump introspection library. With this tool, you can program your own debugging tool precisely tailored to your needs, to determine what's in your app process memory.
You can install this library easily from Nuget, it's called Microsoft.Diagnostics.Runtime.Latest.
I have provided a small WPF sample that displays and refreshes every second all types used by a process, the number of instances of a type, and the size it uses in memory. This is what the tool looks like, it's live sorted on the Size column, so you can see what types eat up the most:
In the sample, I've chosen a process named "ConsoleApplication1", you'll need to adapt that. You could enhance it to take snapshot periodically, build diffs, etc.
Here is the MainWindow.xaml.cs:
public partial class MainWindow : Window
{
private DispatcherTimer _timer = new DispatcherTimer();
private ObservableCollection<Entry> _entries = new ObservableCollection<Entry>();
public MainWindow()
{
InitializeComponent();
var view = CollectionViewSource.GetDefaultView(_entries);
_grid.ItemsSource = view;
// add live sorting on entry's Size
view.SortDescriptions.Add(new SortDescription(nameof(Entry.Size), ListSortDirection.Descending));
((ICollectionViewLiveShaping)view).IsLiveSorting = true;
// refresh every 1000 ms
_timer.Interval = TimeSpan.FromMilliseconds(1000);
_timer.Tick += (s, e) =>
{
// TODO: replace "ConsoleApplication1" by your process name
RefreshHeap("ConsoleApplication1");
};
_timer.Start();
}
private void RefreshHeap(string processName)
{
var process = Process.GetProcessesByName(processName).FirstOrDefault();
if (process == null)
{
_entries.Clear();
return;
}
// needs Microsoft.Diagnostics.Runtime
using (DataTarget target = DataTarget.AttachToProcess(process.Id, 1000, AttachFlag.Passive))
{
// check bitness
if (Environment.Is64BitProcess != (target.PointerSize == 8))
{
_entries.Clear();
return;
}
// read new set of entries
var entries = ReadHeap(target.ClrVersions[0].CreateRuntime());
// freeze old set of entries
var toBeRemoved = _entries.ToList();
// merge updated entries and create new entries
foreach (var entry in entries.Values)
{
var existing = _entries.FirstOrDefault(e => e.Type == entry.Type);
if (existing != null)
{
existing.Count = entry.Count;
existing.Size = entry.Size;
toBeRemoved.Remove(entry);
}
else
{
_entries.Add(entry);
}
}
// purge old entries
toBeRemoved.ForEach(e => _entries.Remove(e));
}
}
// read the heap and construct a list of entries per CLR type
private static Dictionary<ClrType, Entry> ReadHeap(ClrRuntime runtime)
{
ClrHeap heap = runtime.GetHeap();
var entries = new Dictionary<ClrType, Entry>();
try
{
foreach (var seg in heap.Segments)
{
for (ulong obj = seg.FirstObject; obj != 0; obj = seg.NextObject(obj))
{
ClrType type = heap.GetObjectType(obj);
if (type == null)
continue;
Entry entry;
if (!entries.TryGetValue(type, out entry))
{
entry = new Entry();
entry.Type = type;
entries.Add(type, entry);
}
entry.Count++;
entry.Size += (long)type.GetSize(obj);
}
}
}
catch
{
// exceptions can happen if the process is dying
}
return entries;
}
}
public class Entry : INotifyPropertyChanged
{
private long _size;
private int _count;
public event PropertyChangedEventHandler PropertyChanged;
public ClrType Type { get; set; }
public int Count
{
get { return _count; }
set { if (_count != value) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); _count = value; } }
}
public long Size
{
get { return _size; }
set { if (_size != value) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Size))); _size = value; } }
}
}
Here is the MainWindow.xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="_grid" AutoGenerateColumns="False" IsReadOnly="True" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Size}" Header="Size" Width="2*" />
<DataGridTextColumn Binding="{Binding Count}" Header="Count" Width="*" />
<DataGridTextColumn Binding="{Binding Type}" Header="Type" Width="10*" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>