简体   繁体   English

关于.NET中垃圾收集器的问题(内存泄漏)

[英]Question about the garbage collector in .NET (memory leak)

I guess this is very basic but since I'm learning .NET by myself, I have to ask this question. 我想这是非常基本的,但由于我自己学习.NET,我不得不问这个问题。

I'm used to coding in C, where you have to free() everything. 我习惯用C语言编码,你必须free()所有东西。 In C++/.NET, I have read about the garbage collector. 在C ++ / .NET中,我已经阅读了有关垃圾收集器的内容。 From what I understand, when an instance is no longer used (in the scope of the object), it is freed by the garbage collector. 根据我的理解,当一个实例不再使用时(在对象的范围内),它被垃圾收集器释放。

So, having that in mind, I built a little testing application. 因此,考虑到这一点,我构建了一个小测试应用程序。 But, it seems I didn't get something because when doing the same things a few times (like, opening a form, closing it, reopening it, etc), memory leaks. 但是,似乎我没有得到什么,因为在做同样的事情几次(比如,打开一个表格,关闭它,重新打开它等),内存泄漏。 And big time. 而且很开心。

I tried to look this up on Google but I didn't find anything good for a beginner. 我试着在谷歌上看这个,但我找不到任何对初学者有用的东西。

  1. Is the garbage collector really freeing every objects when no longer used or there are exceptions that I have to handle? 垃圾收集器在不再使用时是否真正释放了每个对象,或者我必须处理异常? What am I missing? 我错过了什么?
  2. Are there free tools to look up for memory leaks? 是否有免费工具来查找内存泄漏?

Yeah, the garbage collector is freeing your objects when they're not used anymore. 是的,当垃圾收集器不再使用时,垃圾收集器会释放它们。

What we usually call a memory leak in .NET is more like: 我们通常称之为.NET中的内存泄漏更像是:

  • You're using external resources (that are not garbage collected) and forgetting to free them. 您正在使用外部资源(不是垃圾收集)而忘记释放它们。 This is solved usually by implementing the IDisposable interface. 这通常通过实现IDisposable接口来解决。
  • You think there aren't references to a given object but indeed there are, somewhere and you're not using them any more but the garbage collector does not know about them. 你认为没有对某个给定对象的引用,但确实存在,某个地方你不再使用它们,但垃圾收集器并不知道它们。

In addition, memory is only reclaimed when needed, meaning the garbage collector activates at given times and performs a collection determining them what memory can be freed and freeing it. 此外,只在需要时回收内存,这意味着垃圾收集器在给定时间激活并执行一个集合,确定它们可以释放哪些内存并释放它。 Until it does, the memory isn't claimed so it might look like memory is being lost. 在此之前,内存未被声明,因此可能看起来内存正在丢失。

Here, I think I'll provide a more complex answer just to clarify. 在这里,我想我会提供一个更复杂的答案来澄清。

First, the garbage collector runs in its own thread. 首先,垃圾收集器在自己的线程中运行。 You have to understand that, in order do reclaim memory the garbage collector needs to stop all other threads so that he can follow up the reference chain an determine what depends on what. 你必须明白,为了回收内存,垃圾收集器需要停止所有其他线程,以便他可以跟进引用链,确定什么取决于什么。 That's the reason memory isn't freed right away, a garbage collector imply certain costs in performance. 这就是内存没有立即释放的原因,垃圾收集器意味着性能的某些成本。

Internally the garbage collector manage memory in generations. 垃圾收集器在内部管理内存。 In a very simplified way there are several generations for long lived, short lived and big size objects. 以非常简单的方式,对于长寿命,短寿命和大尺寸物体,有几代。 The garbage collector moves the object from one generation to another each time its performs a collection which happens more often for short lived generation that for long lived one. 垃圾收集器每次执行收集时都会将对象从一代移动到另一代,这种收集更常发生在短期生成中,对于长期存在的生成。 It also reallocates objects to get you the most possible contiguous space so again, performing a collection is a costly process. 它还重新分配对象以获得最可能的连续空间,因此执行集合是一个代价高昂的过程。

If you really want to see the effects of you freeing the form (when getting out of scope and having no more reference to it) you can call GC.Collect() . 如果你真的想看到释放表单的效果(当超出范围并且没有更多引用它时),你可以调用GC.Collect() Do that just for testing, it's highly unwise to call Collect except for a very few cases where you know exactly what you're doing and the implications it will have. 这样做只是为了测试,调用Collect是非常不明智的,除了极少数情况,你知道你正在做什么以及它将产生什么影响。

A little more explaining about the Dispose method of the IDispose interface. 稍微解释一下IDispose接口的Dispose方法。

Dispose isn't a destructor in the usual C++ way, it isn't a destructor at all. Dispose不是通常的C ++方式的析构函数,它根本不是析构函数。 Dispose is a way to deterministically get rid of unmanaged objects . Dispose是一种确定性地摆脱非托管对象的方法 An example: Suppose you call an external COM library that happens to allocate 1GB of memory due to what it is doing. 示例:假设您调用的外部COM库恰好分配1GB内存,因为它正在做什么。 If you have no Dispose that memory will sit there, wasting space until the GC inits a collection and reclaims the unmanaged memory by calling the actual object destructor. 如果没有Dispose,那么内存就会停留在那里,浪费空间直到GC进入集合并通过调用实际的对象析构函数来回收非托管内存。 So if you want to free the memory right away you have to call the Dispose method but you're not "forced" to do so. 因此,如果你想立即释放内存,你必须调用Dispose方法,但你并没有“强迫”这样做。

If you don't use IDisposable interface then you have to free you're unmanaged resources in the Finalize method. 如果您不使用IDisposable接口,则必须在Finalize方法中释放您的非托管资源。 Finalize is automatically called from the object destructor when the GC attempts to reclaim the object. 当GC尝试回收对象时,会自动从对象析构函数调用Finalize。 So if you have a proper finalize method the unmanaged memory will get freed either way. 因此,如果你有一个正确的finalize方法,非托管内存将被释放。 Calling Dispose will only make it deterministic. 调用Dispose只会使它具有确定性。

What leads you to conclude that there are memory leaks? 是什么导致您得出内存泄漏的结论? Under garbage collection, there is no guarantee that memory is freed immediately , and in general the GC doesn't kick in until your process memory reaches some threshold, or the available heap has been exhausted. 在垃圾收集下,无法保证内存立即被释放,并且通常GC在您的进程内存达到某个阈值或可用堆已用完之前不会启动。 (The exact heuristic is complicated and not important.) So the fact that your process's memory goes up and doesn't go down doesn't necessarily mean that there's a bug. (确切的启发式方法很复杂而且不重要。)因此,您的进程内存上升但不会下降这一事实并不一定意味着存在错误。 It might just be that the GC didn't get around to cleaning up your process yet. 可能只是因为GC还没有开始清理你的过程。

Additionally, are you sure that there are no references to your objects? 此外,您确定没有对您的对象的引用吗? It's possible that you have references that you aren't aware of. 你可能有你不知道的参考文献。 Most memory leaks in .NET applications are because people don't realize that their memory is still being referenced somewhere. .NET应用程序中的大多数内存泄漏都是因为人们没有意识到他们的内存仍在某处被引用。

Task Manager is a terrible way to examine your memory usage. 任务管理器是一种检查内存使用情况的可怕方法。 If you want to study how the garbage collector works, install the CLR Profiler and use it to analyze your application. 如果要研究垃圾收集器的工作原理,请安装CLR Profiler并使用它来分析您的应用程序。 It will tell you exactly what the garbage collector is doing. 它会告诉你垃圾收集器到底在做什么。

See How To: Use CLR Profiler . 请参见如何:使用CLR Profiler

I'm adding this as an answer rather than a comment on the question, but this follows-up a question asked by the OP in a comment: using statement on MSDN. 我将此作为答案而不是对问题的评论添加,但这是OP在评论中提出的一个问题:在MSDN上使用语句。 IDisposable interface on MSDN. MSDN上的IDisposable接口。

Here is the crux of the issue: what you're used to as far as object destructors is gone. 这就是问题的症结所在:对于对象析构函数你已经习惯了。 Everything you've been told about how to code correctly is screaming up from your subconscious, saying this can't be true, and if it is true, it's wrong and terrible. 你被告知如何正确编码的一切都是从你的潜意识中尖叫起来,说这不可能是真的,如果是真的,这是错误的和可怕的。 It's very different; 这是非常不同的; it's hard to remember how much I really despised it at first (I was a proud C++ developer). 很难记住我一开始真的鄙视它(我是一个自豪的C ++开发人员)。

I personally promise you: it's going to be OK! 我个人向​​你保证:一切都会好的!

Here's another good thing to read: Destructors and Finalizers in Visual C++ . 这是另一个好东西: Visual C ++中的析构函数和终结函数

The GC will not necessarily actually free things at the moment the object is no longer referenced. 在不再引用对象时,GC实际上不一定会释放内容。 The GC will collect it at some time in the future - you don't know exactly when, but if memory is needed, the GC will perform a collection if necessary. GC将在以后的某个时间收集它 - 您不确切知道何时,但如果需要内存,GC将在必要时执行收集。

If you just want to figure out if you have a memory leak or not, have a look at perfmon which ships with your copy of windows: 如果您只想弄清楚是否有内存泄漏,请查看随您的Windows副本一起提供的perfmon

In particular the .NET CLR Memory counter bytes in all heaps , if this number is steadily growing you have a leak. 特别是所有堆中的.NET CLR内存计数器字节 ,如果此数字稳步增长,则会出现泄漏。

You can even dig deeper by comparing the Gen 2 heap size to the Large Object Heap Size. 您甚至可以通过将Gen 2堆大小与Large Object Heap Size进行比较来深入挖掘。 If the former is growing steadily you have a large blobs of data leaking. 如果前者稳步增长,则会出现大量数据泄露。

Once you confirm there is a leak, you can attach with windbg and use the sos extensions to determine what is leaking. 一旦确认存在泄漏,您可以使用windbg附加并使用sos扩展来确定泄漏的内容。

If you can afford to spend a few bucks have a look at the .NET Memory Profiler . 如果你能花几块钱看一下.NET Memory Profiler

有一些免费工具可以查看.Net中的托管堆,使用WinDBGSOSSOSEX扩展,可以运行一个程序,暂停它,然后查看堆栈中存在哪些对象,(更重要的是)哪个其他对象持有对它们(根)的引用,这将阻止GC收集它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM