当我运行我的应用程序时,“进程内存”图中显示了无法预料和意外数量的垃圾收集活动,这让我想知道程序中生成的垃圾在哪里,因为我觉得我没有程序中的内存泄漏。有人可以告诉我是否有办法查看我的代码中生成垃圾的部分(或行)?
提前致谢。
答案 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();
}
}
}
因此,您可以看到两个快照(相隔大约5秒)218,242个字符串,char []和由垃圾收集器收集的Program对象。通过单击字符串,我们可以看到创建对象的调用堆栈。 (请注意,您需要启用“收集分配数据”选项以查看这些调用堆栈,如果没有它,您将获得总数而不是对象来自的位置)
答案 1 :(得分:7)
您可以使用Microsoft的CLR MD,一个运行时进程和崩溃转储内省库。使用此工具,您可以根据自己的需要编写自己的调试工具,以确定应用程序进程内存中的内容。
您可以从Nuget轻松安装此库,它名为Microsoft.Diagnostics.Runtime.Latest。
我提供了一个小的WPF示例,它显示和刷新进程使用的所有类型,类型的实例数以及它在内存中使用的大小。这就是该工具的样子,它在尺寸列上实时排序,因此您可以看到哪些类型吃得最多:
在示例中,我选择了名为" 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