Answers:
如果您有充分的理由相信大量对象(尤其是您怀疑属于第1代和第2代的对象)现在可以进行垃圾收集了,那么就性能损失较小而言,现在是适当的收集时间。
一个很好的例子是,如果您刚刚关闭了一个大表格。您知道现在可以对所有UI控件进行垃圾回收,并且由于关闭表单而造成的短暂暂停对于用户而言可能不会引起注意。
更新2.7.2018
从.NET 4.5开始- GCLatencyMode.LowLatency
和GCLatencyMode.SustainedLowLatency
。进入和退出这两种模式时,建议您使用强制使用完整的GC GC.Collect(2, GCCollectionMode.Forced)
。
从.NET 4.6开始-有GC.TryStartNoGCRegion
方法(用于设置只读值GCLatencyMode.NoGCRegion
)。这本身可以执行完整的阻塞垃圾回收,以释放足够的内存,但是鉴于我们在一段时间内不允许使用GC,因此我认为在此之前和之后执行完整的GC也不错。
资料来源:微软工程师本·沃森(Ben Watson):编写高性能.NET代码,第二版。2018。
看到:
GC.Collect
您基本上是在告诉垃圾收集器您知道比改变更好。你为什么不喜欢这个例子?
GC.Collect()
将要求GC执行完整收集。如果您知道自己刚刚制作了许多可以进行垃圾收集的长期存在的对象,并且您相信用户现在不太可能注意到比现在稍稍暂停的时间,那么认为现在是更好的做法似乎是完全合理的。是时候提示收集而不是让它稍后发生。
我GC.Collect
仅在编写粗略的性能/探查器测试装备时使用;即我有两个(或更多)代码块要测试-类似:
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
TestA(); // may allocate lots of transient objects
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
TestB(); // may allocate lots of transient objects
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
...
所以这TestA()
并TestB()
与类似状态下运行-即TestB()
没有得到,只是因为敲定TestA
离开它非常接近临界点。
一个经典的示例是一个简单的控制台exe(一种Main
足以在此处发布的方法,例如sort-up),它显示了循环字符串串联和之间的区别StringBuilder
。
如果我需要一些精确的信息,那么这将是两个完全独立的测试-但是如果我们只想在测试过程中最小化(或标准化)GC以大致了解其行为,通常这就足够了。
在生产代码期间?我尚未使用它; -p
最佳做法是在大多数情况下不要强制垃圾回收。 (我研究过的每个系统都强制执行垃圾收集,着重强调了以下问题:如果解决了这些问题,将消除了强制执行垃圾收集的需求,并大大加快了系统运行速度。)
在某些情况下,您比垃圾收集器更了解内存使用情况。在多用户应用程序或一次响应一个以上请求的服务中,这不太可能是正确的。
但是,在某些批处理中,您对GC的了解更多。例如考虑一个应用程序。
您可能可以进行案例分析(经过仔细的测试),在处理完每个文件后应强制执行完整的垃圾回收。
另一种情况是,该服务每隔几分钟醒来以处理一些项目,并且在睡眠时不保持任何状态。然后在临睡前强制进行一次完整的收集可能是值得的。
我唯一考虑强制收集的时间是当我知道最近创建了很多对象并且当前仅引用了很少的对象时。
当我可以向我提供有关此类事情的提示而不必强迫自己使用GC时,我宁愿拥有一个垃圾收集API。
另请参阅“ Rico Mariani的演奏花絮 ”
一种情况是,当您尝试对使用WeakReference的代码进行单元测试时。
在大型24/7或24/6系统中-对消息,RPC请求作出反应或连续轮询数据库或进程的系统-拥有一种识别内存泄漏的方法很有用。为此,我倾向于向应用程序中添加一种机制来临时挂起任何处理,然后执行完整的垃圾回收。这会使系统进入静态状态,在该状态下,剩余的内存要么是合法的长期存在的内存(高速缓存,配置等),要么被“泄漏”(不希望或希望被扎根但实际上已经扎根的对象)。
有了这种机制,就可以更轻松地分析内存使用情况,因为报表不会被活动处理产生的噪音所笼罩。
为确保获得所有垃圾,您需要执行两个收集:
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
因为第一个集合将使带有终结器的所有对象都被终结(但实际上不会垃圾收集这些对象)。第二个GC将垃圾收集这些最终对象。
看看Rico Mariani的这篇文章。当调用GC.Collect时,他给出了两个规则(规则1是:“不要”):
通过Interop自动化Microsoft Office时,几乎需要调用GC.Collect()的一个实例。Office的COM对象不喜欢自动释放,并且可能导致Office产品实例占用大量内存。我不确定这是问题还是设计使然。互联网上有很多关于此主题的文章,因此我不会赘述。
使用Interop进行编程时,通常应通过使用Marshal.ReleseComObject()来手动释放每个 COM对象。另外,手动调用垃圾回收可以帮助“清理”。使用完Interop对象后,调用以下代码似乎会有所帮助:
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
以我个人的经验,结合使用ReleaseComObject和手动调用垃圾回收会大大减少Office产品(特别是Excel)的内存使用量。
我正在对数组和列表进行一些性能测试:
private static int count = 100000000;
private static List<int> GetSomeNumbers_List_int()
{
var lstNumbers = new List<int>();
for(var i = 1; i <= count; i++)
{
lstNumbers.Add(i);
}
return lstNumbers;
}
private static int[] GetSomeNumbers_Array()
{
var lstNumbers = new int[count];
for (var i = 1; i <= count; i++)
{
lstNumbers[i-1] = i + 1;
}
return lstNumbers;
}
private static int[] GetSomeNumbers_Enumerable_Range()
{
return Enumerable.Range(1, count).ToArray();
}
static void performance_100_Million()
{
var sw = new Stopwatch();
sw.Start();
var numbers1 = GetSomeNumbers_List_int();
sw.Stop();
//numbers1 = null;
//GC.Collect();
Console.WriteLine(String.Format("\"List<int>\" took {0} milliseconds", sw.ElapsedMilliseconds));
sw.Reset();
sw.Start();
var numbers2 = GetSomeNumbers_Array();
sw.Stop();
//numbers2 = null;
//GC.Collect();
Console.WriteLine(String.Format("\"int[]\" took {0} milliseconds", sw.ElapsedMilliseconds));
sw.Reset();
sw.Start();
//getting System.OutOfMemoryException in GetSomeNumbers_Enumerable_Range method
var numbers3 = GetSomeNumbers_Enumerable_Range();
sw.Stop();
//numbers3 = null;
//GC.Collect();
Console.WriteLine(String.Format("\"int[]\" Enumerable.Range took {0} milliseconds", sw.ElapsedMilliseconds));
}
并且我进入OutOfMemoryException
GetSomeNumbers_Enumerable_Range方法,唯一的解决方法是通过以下方式取消分配内存:
numbers = null;
GC.Collect();
在您的示例中,我认为调用GC.Collect不是问题,而是存在设计问题。
如果您要间隔一定的时间(设置时间)醒来,则应将程序设计为执行一次(执行一次任务),然后终止。然后,将程序设置为预定任务,以预定间隔运行。
这样,您不必担心调用GC.Collect(这是您应该很少要做的事情)。
话虽如此,Rico Mariani在这个主题上有一篇很棒的博客文章,可以在这里找到:
当您要验证自己没有造成内存泄漏时(例如,如果您正在使用WeakReferences或ConditionalWeakTable进行某些操作,动态生成的代码等),则在单元测试中可以调用GC.Collect()的一个有用的地方。
例如,我有一些测试,例如:
WeakReference w = CodeThatShouldNotMemoryLeak();
Assert.IsTrue(w.IsAlive);
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsFalse(w.IsAlive);
可以说使用WeakReferences本身就是一个问题,但是似乎如果您要创建一个依赖这种行为的系统,则调用GC.Collect()是验证此类代码的好方法。
Scott Holden 关于何时(以及何时不)调用GC.Collect的博客条目特定于.NET Compact Framework,但规则通常适用于所有托管开发。
在某些情况下,安全比后悔好。
这是一种情况。
可以使用IL重写在C#中编写非托管 DLL(因为在某些情况下有必要这样做)。
现在假设,例如,DLL在类级别创建了一个字节数组-因为许多导出的函数都需要访问此类。DLL卸载后会发生什么?此时是否自动调用垃圾收集器?我不知道,但是作为非托管 DLL,完全有可能不调用GC。如果不调用它将是一个大问题。当DLL被卸载时,垃圾收集器也将被卸载-那么谁来负责收集任何可能的垃圾以及它们将如何处理?最好使用C#的垃圾收集器。具有清理功能(DLL客户端可用),其中将类级别的变量设置为null并调用垃圾回收器。
安全胜过遗憾。
我对此还不确定。我在应用服务器上工作了7年。我们更大的安装使用24 GB Ram。它的高度多线程和所有对GC.Collect()的调用都遇到了非常可怕的性能问题。
当许多第三方组件认为现在明智的做法是使用GC.Collect()时。因此,一堆简单的Excel报表在一分钟内几次阻塞了所有线程的App Server。
为了删除GC.Collect()调用,我们必须重构所有第3方组件,并且所有组件在此之后都可以正常工作。
但是我也在Win32上运行服务器,在这里,我在获得OutOfMemoryException之后开始大量使用GC.Collect()。
但是我对此也不太确定,因为我经常注意到,当我在32位上获得OOM时,我再次尝试运行相同的操作,而没有调用GC.Collect(),它运行良好。
我想知道的一件事是OOM异常本身...如果我已经编写了.Net Framework,并且无法分配内存块,则将使用GC.Collect(),碎片整理内存(??),然后重试,如果我仍然找不到可用的内存块,那么我将抛出OOM-Exception。
由于GC.Collect的性能问题的缺点,或者至少将此行为作为可配置选项。
现在,我的应用中有很多类似的代码可以“解决”问题:
public static TResult ExecuteOOMAware<T1, T2, TResult>(Func<T1,T2 ,TResult> func, T1 a1, T2 a2)
{
int oomCounter = 0;
int maxOOMRetries = 10;
do
{
try
{
return func(a1, a2);
}
catch (OutOfMemoryException)
{
oomCounter++;
if (maxOOMRetries > 10)
{
throw;
}
else
{
Log.Info("OutOfMemory-Exception caught, Trying to fix. Counter: " + oomCounter.ToString());
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(oomCounter * 10));
GC.Collect();
}
}
} while (oomCounter < maxOOMRetries);
// never gets hitted.
return default(TResult);
}
(请注意,Thread.Sleep()行为确实是App的特定行为,因为我们正在运行ORM缓存服务,并且如果RAM超出某些预定义的值,该服务将花费一些时间来释放所有缓存的对象。因此,它等待第一次需要几秒钟,并且每次发生OOM都会增加等待时间。)
GC.Collect
。由于它具有广泛的应用程序影响,因此只有应用程序才能这样做(如果有的话)。
If i would have written the .Net Framework, and i can't alloc a memory block, i would use GC.Collect(),
-我认为他们已经在这样做-我已经看到迹象表明内部GC触发器之一是分配内存的某些失败。
您应该尝试避免使用GC.Collect(),因为它非常昂贵。这是一个例子:
public void ClearFrame(ulong timeStamp)
{
if (RecordSet.Count <= 0) return;
if (Limit == false)
{
var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000;
if (seconds <= _preFramesTime) return;
Limit = true;
do
{
RecordSet.Remove(RecordSet[0]);
} while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime);
}
else
{
RecordSet.Remove(RecordSet[0]);
}
GC.Collect(); // AVOID
}
测试结果:CPU使用率12%
当您更改为此:
public void ClearFrame(ulong timeStamp)
{
if (RecordSet.Count <= 0) return;
if (Limit == false)
{
var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000;
if (seconds <= _preFramesTime) return;
Limit = true;
do
{
RecordSet[0].Dispose(); // Bitmap destroyed!
RecordSet.Remove(RecordSet[0]);
} while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime);
}
else
{
RecordSet[0].Dispose(); // Bitmap destroyed!
RecordSet.Remove(RecordSet[0]);
}
//GC.Collect();
}
测试结果:CPU使用率2-3%
另一个原因是当您在USB COM端口上打开了SerialPort,然后拔下了USB设备。由于打开了SerialPort,因此资源在系统注册表中保留了对先前连接的端口的引用。然后,系统的注册表将包含过时的数据,因此可用端口的列表将是错误的。因此,必须关闭端口。
在端口上调用SerialPort.Close()会在对象上调用Dispose(),但是它将一直保留在内存中,直到垃圾回收实际运行为止,从而导致注册表保持陈旧状态,直到垃圾回收器决定释放资源为止。
从https://stackoverflow.com/a/58810699/8685342:
try
{
if (port != null)
port.Close(); //this will throw an exception if the port was unplugged
}
catch (Exception ex) //of type 'System.IO.IOException'
{
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
}
port = null;
如果您使用的.net版本低于4.5,则手动收集可能是不可避免的(尤其是当您处理许多“大对象”时)。
该链接描述了原因:
https://blogs.msdn.microsoft.com/dotnet/2011/10/03/large-object-heap-improvements-in-net-4-5/
由于有小对象堆(SOH)和大对象堆(LOH)
我们可以调用GC.Collect()清除SOP中的取消引用对象,然后将活动对象移动到下一代。
在.net4.5中,我们还可以通过使用largeobjectheapcompactionmode来压缩LOH