A unforeseen and unanticipated amount of garbage collection activity is shown in the 'Process Memory' graph when I run my application which makes me want to know where in the program is the garbage generated as I don't feel that I have any memory leaks in the program. Can someone please tell me if there is a way to view the parts (or lines) of my code where garbage is generated?
Thanks in advance.
Pretty much any memory profiler will show this info. Just look for a list of "Dead objects" between two snapshots and that is the list of "garbage" that was generated and will need to be collected by the GC.
I personally use DotMemory by JetBrains.
For example with the following program
using System;
namespace SandboxConsole
{
class Program
{
private int _test;
static void Main(string[] args)
{
var rnd = new Random();
while (true)
{
var obj = new Program();
obj._test = rnd.Next();
Console.WriteLine(obj);
}
}
public override string ToString()
{
return _test.ToString();
}
}
}
It gave me a output like
So you can see between the two snapshots (that where about 5 seconds apart) 218,242 strings, char[]s, and Program objects where collected by the garbage collector. and by clicking on strings we can see the call stacks where the objects where created. (note you do need to enable the "collect allocation data" option to see those call stacks, without it you get the total numbers but not where the objects came from)
What 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>
System.GC contains the garbage collection object, and there are a number of static methods you can use to have direct control over the process.
void GC::Collect() invokes the GC for all generations, while void GC::Collect(int Generation) invokes it only up to and including the generation you specify.
else
use this command !eeheap -gc in terminal