当前位置: 首页 > 工具软件 > collect > 使用案例 >

关于c#:GC.Collect()之后是否立即运行垃圾回收?

巢睿
2023-12-01

用C#写了一个运用ICE组件进行接口通信的服务程序,程序运行很正常,可是在客户端调用ICE接口时出现了大量的数据丢失,而且偶尔还通信不上,服务端最明显的现象就是telnet服务的通信端口时不通(cmd窗口一闪而过),经过大量时间的跟踪测试,最终只能通过tfs上的历史修改记录来一步一步恢复还原,最后问题定位在GC.Collect();这一句代码上:大部份接口都存在这一句代码进行内存回收,而把这句注掉以后,通信和数据传输都正常下来了!

    MSDN对于强制垃圾回收的解释:

    垃圾回收 GC 类提供 GC.Collect 方法,您可以使用该方法让应用程序在一定程度上直接控制垃圾回收器。通常情况下,您应该避免调用任何回收方法,让垃圾回收器独立运行。在大多数情况下,垃圾回收器在确定执行回收的最佳时机方面更有优势。但是,在某些不常发生的情况下,强制回收可以提高应用程序的性能。当应用程序代码中某个确定的点上使用的内存量大量减少时,在这种情况下使用 GC.Collect 方法可能比较合适。例如,应用程序可能使用引用大量非托管资源的文档。当您的应用程序关闭该文档时,您完全知道已经不再需要文档曾使用的资源了。出于性能的原因,一次全部释放这些资源很有意义。有关更多信息,请参见 GC.Collect 方法。

    在垃圾回收器执行回收之前,它会挂起当前正在执行的所有线程。如果不必要地多次调用 GC.Collect,这可能会造成性能问题。您还应该注意不要将调用 GC.Collect 的代码放置在程序中用户可以经常调用的点上。这可能会削弱垃圾回收器中优化引擎的作用,而垃圾回收器可以确定运行垃圾回收的最佳时间。

    另外做一些扩展阅读:

GC工作方式

首先我们要知道托管代码中的对象什么时候回收我们管不了(除非用GC.Collect强迫GC回收,这不推荐,后面会说明为什么)。GC会在它"高兴"的时候执行一次回收(这有许多原因,比如内存不够用时。这样做是为了提高内存分配、回收的效率)。那么如果我们用Destructor呢?同样不行,因为.NET中Destructor的概念已经不存在了,它变成了Finalizer,这会在后面讲到。目前请记住一个对象只有在没有任何引用的情况下才能够被回收。为了说明这一点请看下面这一段代码:[C#]

 object objA = new object();
 object objB = objA;
 objA = null;
 // 强迫回收。
 GC.Collect();
 objB.ToString();


 [Visual Basic]
 Dim objA As New Object()
 Dim objB As Object = objA
 objA = Nothing
 ' 强迫回收。
 GC.Collect()objB.ToString()

        这里objA引用的对象并没有被回收,因为这个对象还有另一个引用,ObjB。对象在没有任何引用后就有条件被回收了。当GC回收时,它会做以下几步:确定对象没有任何引用。检查对象是否在Finalizer表上有记录。如果在Finalizer表上有记录,那么将记录移到另外的一张表上,在这里我们叫它Finalizer2。如果不在Finalizer2表上有记录,那么释放内存。在Finalizer2表上的对象的Finalizer会在另外一个low priority的线程上执行后从表上删除。当对象被创建时GC会检查对象是否有Finalizer,如果有就会在Finalizer表中添加纪录。我们这里所说的记录其实就是指针。如果仔细看这几个步骤,我们就会发现有Finalizer的对象第一次不会被回收,也就是,有Finalizer的对象要一次以上的Collect操作才会被回收,这样就要慢一步,所以作者推荐除非是绝对需要不要创建Finalizer。为了证明GC确实这么工作而不是作者胡说,我们将在对象的复活一章中给出一个示例,眼见为实,耳听为虚嘛!^_^GC为了提高回收的效率使用了Generation的概念,原理是这样的,第一次回收之前创建的对象属于Generation 0,之后,每次回收时这个Generation的号码就会向后挪一,也就是说,第二次回收时原来的Generation 0变成了Generation 1,而在第一次回收后和第二次回收前创建的对象将属于Generation 0。GC会先试着在属于Generation 0的对象中回收,因为这些是最新的,所以最有可能会被回收,比如一些函数中的局部变量在退出函数时就没有引用了(可被回收)。如果在Generation 0中回收了足够的内存,那么GC就不会再接着回收了,如果回收的还不够,那么GC就试着在Generation 1中回收,如果还不够就在Generation 2中回收,以此类推。Generation也有个最大限制,根据Framework版本而定,可以用GC.MaxGeneration获得。在回收了内存之后GC会重新排整内存,让数据间没有空格,这样是因为CLR顺序分配内存,所以内存之间不能有空着的内存。现在我们知道每次回收时都会浪费一定的CPU时间,这就是我说的一般不要手动GC.Collect的原因(除非你也像我一样,写一些有关GC的示例!^_^)。‍

Does garbage collection run immediately after GC.Collect()?

这个问题只是出于研究目的。

我读过很多有关C#的书,这个问题总是浮现在我脑海。据我了解,C#是托管代码,并且所有垃圾收集都是在CLR决定何时运行垃圾收集时发生的。开始吧。

假设我有一个简单的类Student:

public class Student
{
	public int IdStudent { get; set; }
	public string Name { get; set; }
	public string Surname { get; set; }
}
class Program
{
	static void Main(string[] args)
	{
		This is row1:    Person person = new Person() { IdPerson = 1, Name = "Bill", SurName = "Collins" };
		This is row2:    System.GC.Collect();
		This is row3:    string str = "Hello World!";
	}
}


我对不立即在row2运行垃圾收集是否正确?请批准或拒绝我的假设:

  • GC.Collect()只是进行垃圾收集的请求,该垃圾收集不会在row2上立即运行。该行可能以x毫秒/秒执行。
    在我看来,方法System.GC.Collect();只是对垃圾收集器说垃圾收集器应该运行垃圾收集,但是真正的垃圾收集可能会在x毫秒/秒内发生

  • 只有垃圾收集器知道何时将运行垃圾收集。并且,如果第0代中有可用空间,则在第2行中不会进行垃圾回收:row2: System.GC.Collect();

  • 由于我们在托管环境中进行编程,因此不可能立即运行垃圾回收,只有CLR决定何时运行垃圾回收。垃圾回收可以以x毫秒/秒的速度运行,或者垃圾回收可能无法运行,原因是调用方法GC.Collect()后,第0代中有足够的空间来创建新对象。程序员可以做的就是只要求CLR通过方法GC.Collect()运行垃圾回收。

更新:

我已经阅读了有关GC.Collect Method ()的msdn这篇文章。但是,我不清楚何时开始真正清除未引用的objets。 MSDN说:

GC.Collect Method () forces an immediate garbage collection of all
generations.

但是,在"备注"中,我已经阅读了以下内容:

Use this method to try to reclaim all memory that is inaccessible. It
performs a blocking garbage collection of all generations.

  • 我对此"使用此方法进行尝试"感到困惑,并且我认为可能不会发生垃圾回收,因为CLR决定有足够的空间来创建新对象。我对吗?

 相关讨论


短答案

调用GC.Collect()将执行完整的垃圾回收并等待其完成,但不会等待任何未完成的终结器运行。

长答案

您的假设部分正确,因为用于运行终结器的GC在一个或多个后台线程中运行。 (但请参阅此答案末尾的脚注。)

但是,可以在调用GC.Collect()之后通过调用GC.WaitForFullGCComplete()和GC.WaitForPendingFinalizers()等待完整的GC完成:

GC.Collect();
GC.WaitForPendingFinalizers();
GC.WaitForFullGCComplete();


请注意,通常不应以这种方式使用GC。我认为您有一个特殊情况需要解决,或者您出于研究目的而这样做。但是,请注意,未指定运行终结器的线程,因此不能保证此方法将终止。

我看到的唯一有效的情况是应用程序关闭时,您想要(尝试)确保所有终结器都已运行-例如,因为它们将刷新日志文件等。

如上所述,这仍然不能保证所有终结器都已运行。这是您所能做的最好的事情。

回答您的观点(5):

GC.Collect()的文档指出:

强制立即进行所有世代的垃圾收集。

因此,这将强制执行GC。

该文档还指出:

使用此方法尝试回收所有不可访问的内存。

在此使用" try"一词仅意味着即使运行了完整的GC,也不一定会收回所有无法访问的内存。可能有多种原因,例如,终结器可能会阻塞。

脚注

.Net 4.5允许您指定GC.Collect()是否被阻止。

实际上,GC.Collect()的文档指出它执行了所有世代的阻塞垃圾收集,这似乎与我上面的陈述相矛盾。但是,对于是否确实如此,似乎有些困惑。

例如参见该线程。

答案是这样的:默认情况下,GC.Collect()将等待所有世代的GC,但它不会等待待决的终结器(始终在单独的线程中执行)。

因此,如果您不需要等待终结器,则只需调用GC.Collect(),而无需等待其他任何东西。

 相关讨论


有两个GC,从您的代码中,我相信您想了解Workstation GC。通过在完整收集期间同时运行,可以最大程度地减少暂停时间?工作站GC使用第二个处理器同时运行收集,从而最大程度地减少了延迟,同时降低了吞吐量。如果服务器GC工作不正常,我们应该只担心GC行为。如果按照工作站GC在代码中添加GC.collect(),则在Server GC上可能没有用。

服务器GC旨在实现最大吞吐量,并具有很高的性能进行扩展。与工作站相比,服务器上的内存碎片问题要严重得多,这使垃圾回收成为一个有吸引力的提议。在单处理器方案中,两个收集器的工作方式相同:工作站模式,没有并发收集

I am confused by this Use this method to TRY and I think that garbage collection may not occur cause CLR decides that there is enough space to create new objects. Am I right?

对于工作区GC,GC.Collect将尽快开始收集,您可以放心地假设它会立即收集。

 类似资料: