一般建议不要在代码中调用GC.Collect
,但有哪些特例情况呢?
我只能想到几种非常特殊的情况,在这些情况下强制进行垃圾回收可能是有意义的。
其中一个例子是服务会以间隔时间唤醒,执行一些任务,然后休眠很长时间。在这种情况下,强制进行垃圾回收可能是个好主意,以防止即将变得空闲的进程占用比需要更多的内存。
除了这种情况之外,还有什么其他情况可以接受调用GC.Collect
吗?
一般建议不要在代码中调用GC.Collect
,但有哪些特例情况呢?
我只能想到几种非常特殊的情况,在这些情况下强制进行垃圾回收可能是有意义的。
其中一个例子是服务会以间隔时间唤醒,执行一些任务,然后休眠很长时间。在这种情况下,强制进行垃圾回收可能是个好主意,以防止即将变得空闲的进程占用比需要更多的内存。
除了这种情况之外,还有什么其他情况可以接受调用GC.Collect
吗?
尽可能避免使用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%
一个调用GC.Collect()的有用场景是在单元测试中,当您想要验证您没有创建内存泄漏时(例如如果您正在使用WeakReferences或ConditionalWeakTable、动态生成的代码等),您可以这样做。
例如,我有一些像这样的测试:
WeakReference w = CodeThatShouldNotMemoryLeak();
Assert.IsTrue(w.IsAlive);
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsFalse(w.IsAlive);
可以说使用弱引用本身就是一个问题,但似乎如果您正在创建依赖于此行为的系统,则调用GC.Collect()是验证此类代码的一种好方法。
如果您正在创建大量的新System.Drawing.Bitmap
对象,则垃圾回收器不会清除它们。最终,GDI+将认为您的内存不足,并抛出“参数无效”的异常。定期(不要太频繁!)调用GC.Collect()
似乎可以解决此问题。
using(var stream = new MemoryStream())
{
bitmap.Save(stream, ImageFormat.Png);
techObject.Last().Image = Image.FromStream(stream);
bitmap.Dispose();
// Without this code, I had an OutOfMemory exception.
GC.Collect();
GC.WaitForPendingFinalizers();
//
}
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;
在一些内存较小的 ARM 计算机上,例如运行 mono 的 Raspberry PI 上,调用垃圾回收器的一个很好的理由是:如果未分配的内存片段占用了太多系统内存,则 Linux 操作系统可能会变得不稳定。我有一个应用程序,需要每秒钟调用 GC 一次,以解决内存溢出问题。
另一个很好的解决方案是,在不再需要时处理对象。不幸的是,在许多情况下这并不容易。
希望至少将此行为作为可配置选项,由于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()的行为是一个非常特定于应用程序的行为,因为我们正在运行ORM缓存服务,并且如果RAM超过一些预定义值,则该服务需要一些时间来释放所有缓存的对象。因此,第一次等待几秒钟,并且每次OOM出现时等待时间都增加。
GC.Collect
。因为它具有应用程序范围的效果,只有应用程序才应该这样做(如果需要的话)。 - CodesInChaos