2017-05-08 38 views
5

当我运行我的应用程序时,无法预见和未预料到的垃圾收集活动量显示在“进程内存”图中,这使我想知道程序中的垃圾产生的位置我不觉得我在程序中有任何内存泄漏。有人可以告诉我是否有办法查看我的代码中产生垃圾的部分(或行)?在c#中查看垃圾收集历史(VS2015)

在此先感谢。

+0

您与[vs2015]标记它。考虑[使用它](https://blogs.msdn.microsoft.com/visualstudioalm/2014/04/02/diagnosing-memory-issues-with-the-new-memory-usage-tool-in-visual-studio/ ),这应该通过比较快照来快速钻取。 –

回答

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秒)218242个字符串,字符[ ]和程序对象,由垃圾收集器收集。通过点击字符串,我们可以看到创建对象的调用堆栈。 (注意你需要启用“收集分配数据”选项来查看这些调用栈,没有它你会得到总数,但不是对象来自哪里)

7

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

您可以很容易地从Nuget安装该库,它被称为Microsoft.Diagnostics.Runtime.Latest

我已经提供了一个小的WPF示例,它显示和刷新每秒一次进程使用的所有类型,类型的实例数量以及它在内存中使用的大小。这是该工具的样子,它是活的排序上大小列,所以你可以看到什么类型的吃起来最:

enter image description here

在示例中,我选择了一个名为进程“ ConsoleApplication1“,你需要修改它。你可以提高它定期拍摄快照,构建diff文件等

这里是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> 
+0

从来没有看过那个图书馆,它看起来很整洁!它是否也可以告诉你哪里的分配只提供总计数? –

+0

@ScottChamberlain - 有很多可用的信息,但是我不认为我们可以确定哪些代码创建了特定类型的实例,如果这就是您的意思。事实上,它与使用调试器控制台(如windbg)可以执行的操作非常相似,但是可以通过编程和.NET来完成。 –

0

系统.GC包含垃圾收集对象,并且有许多静态方法可用于直接控制该进程。

无效GC ::收集()调用所有代GC,而无效GC ::收集(INT代)调用它只是直到并包括您指定的一代。

其他

使用此命令!eeheap-GC在终端