在c#中查看垃圾收集历史记录(VS2015)

时间:2017-05-08 17:46:20

标签: c# visual-studio-2015 garbage-collection garbage

当我运行我的应用程序时,“进程内存”图中显示了无法预料和意外数量的垃圾收集活动,这让我想知道程序中生成的垃圾在哪里,因为我觉得我没有程序中的内存泄漏。有人可以告诉我是否有办法查看我的代码中生成垃圾的部分(或行)?

提前致谢。

3 个答案:

答案 0 :(得分:7)

几乎任何内存分析器都会显示此信息。只需在两个快照之间查找“死对象”列表,这是生成的“垃圾”列表,需要由GC收集。

我个人使用JetBrains的DotMemory

例如,使用以下程序

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();
        }
    }
}

它给了我一个输出 enter image description here

因此,您可以看到两个快照(相隔大约5秒)218,242个字符串,char []和由垃圾收集器收集的Program对象。通过单击字符串,我们可以看到创建对象的调用堆栈。 (请注意,您需要启用“收集分配数据”选项以查看这些调用堆栈,如果没有它,您将获得总数而不是对象来自的位置)

答案 1 :(得分:7)

您可以使用Microsoft的CLR MD,一个运行时进程和崩溃转储内省库。使用此工具,您可以根据自己的需要编写自己的调试工具,以确定应用程序进程内存中的内容。

您可以从Nuget轻松安装此库,它名为Microsoft.Diagnostics.Runtime.Latest

我提供了一个小的WPF示例,它显示和刷新进程使用的所有类型,类型的实例数以及它在内存中使用的大小。这就是该工具的样子,它在尺寸列上实时排序,因此您可以看到哪些类型吃得最多:

enter image description here

在示例中,我选择了名为" ConsoleApplication1"的流程,您需要对其进行调整。您可以增强它以定期拍摄快照,构建差异等。

这是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; } }
    }
}

这是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>

答案 2 :(得分:0)

System.GC 包含垃圾收集对象,您可以使用许多静态方法直接控制该过程。

void GC :: Collect()为所有世代​​调用GC,而 void GC :: Collect(int Generation) 调用它只包括你指定的那一代。

否则

在终端

中使用此命令!eeheap -gc