2017-05-08 34 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在終端