如何释放被占用的内存

3
我在我的项目中有一个主窗口,里面有许多其他的子窗口。
我注意到当我打开主窗口时,占用了1500K的内存,当我打开一个子窗口时,占用的内存增加到了6000K。
当我打开第二个子窗口时,同样的情况发生。但是当我关闭这两个子窗口时,占用的内存并没有被释放。
所以,我希望每当我关闭子窗口时都能释放占用的内存。
请给我一些vb.net代码示例来指导我如何做到这一点。
这个问题经常出现在局域网上的计算机上,而不是我的电脑(开发人员电脑上有SQL服务器)。

现在的时代,6000kB并不算很大!当你有一个小应用程序时,常见程序集的开销会非常高(有很多你没有使用的代码)。此外,“占用内存”不是Windows内存统计数据的术语,如果不知道你正在查看哪个度量标准,很难具体说明(所有内存统计数据都需要谨慎使用:在虚拟内存系统中,没有简单的内存概念,它积极共享和清除页面)。 - Richard
请查看我更新后的答案。在使用非托管资源时实现IDisposable接口,并在类及其构造函数和析构函数名称中进行一些更改,然后无论何时关闭子级,请调用me.disposing(true)。希望这可以帮助。 - user240141
7个回答

13

这个问题的其他答案中有很多错误信息,同时也有一些答案把问题过于复杂化了。在.NET中,垃圾回收存在许多误解,这里所谈论的理论肯定不会有所帮助。

首先,使用Windows任务管理器来分析内存使用是一个巨大的错误。你将得到严重的无效信息,试图根据此信息修改应用程序只会使情况变得更糟,而不是更好。如果你怀疑自己有性能问题(实际上大多数应用程序都不太可能遇到这种情况),那么你需要投资购买一个合适的内存分析器并使用它。

其次,垃圾回收的整个重点在于你不必担心这种事情。你不仅不需要担心它,而且你也不应该担心它。当编写针对.NET Framework的应用程序时,不应进行任何类型的手动内存管理。抵制诱惑去调整垃圾回收器的内部工作方式,并在有人告诉你手动调用GC.Collect以强制进行垃圾回收时,要将手指牢固地插在耳朵里。我想我不应该说永远不要这样做,但几乎没有理由这样做。我更可能怀疑手动调用垃圾回收的代码,而不是其他任何东西。

为什么不应手动调用垃圾回收?好吧,除了显而易见的使用托管语言的整个目的被击败的论点之外,还因为垃圾回收是一项非常缓慢和昂贵的过程。你希望它尽可能地运行,以保持最佳性能。幸运的是,实现垃圾回收算法的程序员比你或我聪明得多,更有经验:他们设计它只在必要时运行,并且不会更频繁地运行。你看不到更频繁运行它的优势,但是你看到缺点。这对于程序员来说应该是完全不透明的。

唯一的例外是当您使用未受垃圾回收器管理或回收的非托管对象时。您可以通过它们实现IDisposable接口来识别这些对象,该接口提供了一个Dispose方法以释放非托管资源。对于公开此方法的对象,应在使用对象后立即调用它。或者更好的做法是,在using语句中包装对象的声明和使用,这将自动处理对象的处理,无论发生什么情况(例如,在使用对象的代码中抛出异常)。
当然,您会注意到Windows Forms库中的几个标准对象实现了IDisposable方法。例如,无处不在的Form提供了一个Dispose方法。但是,这并不一定意味着您需要手动处理这些对象的处理。通常情况下,您只需要显式调用Dispose方法来处理您明确创建的对象 - 易于记忆,对吧?Framework自动创建的对象也会被Framework自动销毁。例如,在设计时放置在Form对象上的控件在其容器窗体关闭时会自动释放。而Form对象本身在关闭时也会自动释放。这与您提出的问题特别相关。Form.Close方法的文档告诉我们:

当一个窗体关闭时,所有在该对象内部创建的资源都会关闭,并且该窗体会被释放。

[...]

当窗体是多文档界面(MDI)应用程序的一部分且窗体不可见时,以及您使用ShowDialog显示窗体时,窗体不会在Close时释放。在这些情况下,您需要手动调用Dispose来标记窗体的所有控件进行垃圾回收。

请注意,通常情况下,您不必从您的代码中手动调用Form.Dispose。当MDI父窗体不可见时,用户无法关闭MDI子窗体,如果在其父窗体不可见时您自己在代码中关闭了该窗体,则可以简单地插入一个调用Form.Dispose的语句。使用ShowDialog方法将表单显示为模态对话框时,可以方便地在using语句中包装其创建和使用。
现在,请记住,仅调用对象上的Dispose方法只会释放非托管资源,并标记该对象可供垃圾回收。这并不会立即释放该对象所占用的内存。这很重要,因为这正是您进行内存分析的关注点所在。您知道对象已被处理,因为您提到变量变得不可用(您说您“失去了它们的值”)。那是因为您无法访问已处理的对象。然而,这并不一定意味着它们所占用的内存已完全释放。这是垃圾回收器的工作,我们已经确定您不应该随意干涉它。它将等待释放内存,直到应用程序处于空闲状态或者它迫切需要重新使用该内存。否则,它会推迟回收,这仍然是可以的。

直接管理对象生命周期有第二个例外情况:有限的非内存资源。例如,除非关闭它(可以通过using语句来实现,这是更好的方法),否则打开的文件在垃圾回收运行之前将无法使用。这样的非内存对象不一定是非托管的(例如,数据库连接可以完全由托管代码实现)。 - Richard
1
@Richard:在这些情况下,几乎所有相关的对象都实现了IDisposable模式,我在我的答案中已经考虑到了这一点。而且,使用using语句是正确的方法,而不是任何类型的手动内存管理(比如自己调用垃圾回收器)。我可能本可以再多说一点,但我开始非常怀疑是否有人会一直读到最后... - Cody Gray
已接受,并且请注意,对于我来说,“手动生命周期管理”就是 IDisposable(这就是为什么我使用了“生命周期”)。但我的评论针对的是您的段落开头:“唯一的例外”刚刚说让系统自己处理它。解释这个问题的问题在于答案必须说“让GC自己做自己的事情,除非”,而且“当”需要涵盖:语法上作用域的 IDisposable,其他 IDisposable 对象(例如,在字段中)和直接使用未托管的对象(因此是终结器的主题);尽管我可能只是简单地提及最后一个“高级”问题。 - Richard
1
@Code Gray,非常好的、扎实的解释。您可以添加更多链接:垃圾回收基础 - http://msdn.microsoft.com/en-us/library/ee787088(v=VS.100).aspx,Dispose、Finalization和资源管理 - http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=88e62cdf-5919-4ac7-bc33-20c06ae539ae,如何在公共语言运行时中识别内存泄漏 - http://support.microsoft.com/kb/318263,如何检测和避免.NET应用程序中的内存和资源泄漏 - http://msdn.microsoft.com/en-us/library/ee658248.aspx。 - Nick Martyshchenko
@GodyGray。我仔细阅读了你的所有答案,当然我发现它非常科学。但让我们回到我的问题,从一开始就是变量值的丢失,当然这不是在我处理任何表单之后发生的,而是在打开表单时进行操作时发生的。这就是为什么我认为我的问题与内存有关?!?! - Lefteris Gkinis

1

利用使用块,自动释放内存

Using { resourcelist | resourceexpression }
    [ statements ]
End Using

2
这并非总是可能的。using {... 内部的资源应该是 IDisposable - Oscar Mederos
@Oscar Mederos -- 这是真的,这就是为什么你需要实现IDisposable的原因...... - Pranay Rana
但是大多数框架类都有IDisposable接口,因此在这种情况下它会很有帮助,对于WinForms,他必须显式调用它。 - user240141
请问您能帮我解决VB.NET的问题吗?因为这是一个关键问题,我需要准确的代码。 - Lefteris Gkinis
@Lefteris Gkinis -- 请前往以下链接获取详细信息:http://msdn.microsoft.com/zh-cn/library/htd05whh.aspx - Pranay Rana

1

那是你不必关心的事情。

.NET Framework 垃圾回收器会为您完成此工作。

.NET Framework的垃圾回收器管理应用程序的内存分配和释放。每次创建新对象时,公共语言运行时从托管堆中为对象分配内存。只要托管堆中有地址空间可用,运行时就会继续为新对象分配空间。

编辑:

您必须确保您不再使用想要释放的资源。垃圾回收器的功能位于命名空间System下的GC类中。

为了调用它,可以执行GC.Collect(),虽然我建议您多了解这个主题,并查看一些示例,比如this one


是的,我会在今天晚些时候完成我的部分,现在我正在办公室工作。 - Lefteris Gkinis
@Lefteris,如果你把你的代码上传到像Pastebin这样的网站,那就太好了,这样我们可以看一下。 - Oscar Mederos
我今天稍后会去做,请等一下。 - Lefteris Gkinis
2
这个答案在所有答案中最接近正确,至少在第一部分是如此。虽然从技术上讲,您提到的GC.Collect确实是手动调用垃圾回收的方法,但是周围没有足够明显的警告文字来说明这种建议的风险。我可以保证,调用GC.Collect不是解决方案。 - Cody Gray
请查看Rico Mariani的《何时调用GC.Collect()》- http://blogs.msdn.com/b/ricom/archive/2004/11/29/271829.aspx - Nick Martyshchenko

1

按照Pranay的建议使用将起作用,因为它会默认调用Dispose方法。否则,在调用子窗体的this.close()之后,您必须显式地调用this.dispose()。但请确保在关闭后不再使用子窗体元素或值,因为dispose最终会清除所有内容。

MSDN处有释放非托管资源的示例

Imports System
Imports System.ComponentModel

' The following example demonstrates how to create
' a resource class that implements the IDisposable interface
' and the IDisposable.Dispose method.
Public Class DisposeExample

   ' A class that implements IDisposable.
   ' By implementing IDisposable, you are announcing that 
   ' instances of this type allocate scarce resources.
   Public Class MyResource
      Implements IDisposable
      ' Pointer to an external unmanaged resource.
      Private handle As IntPtr
      ' Other managed resource this class uses.
      Private component As component
      ' Track whether Dispose has been called.
      Private disposed As Boolean = False

      ' The class constructor.
      Public Sub New(ByVal handle As IntPtr)
         Me.handle = handle
      End Sub

      ' Implement IDisposable.
      ' Do not make this method virtual.
      ' A derived class should not be able to override this method.
      Public Overloads Sub Dispose() Implements IDisposable.Dispose
         Dispose(True)
         ' This object will be cleaned up by the Dispose method.
         ' Therefore, you should call GC.SupressFinalize to
         ' take this object off the finalization queue 
         ' and prevent finalization code for this object
         ' from executing a second time.
         GC.SuppressFinalize(Me)
      End Sub

      ' Dispose(bool disposing) executes in two distinct scenarios.
      ' If disposing equals true, the method has been called directly
      ' or indirectly by a user's code. Managed and unmanaged resources
      ' can be disposed.
      ' If disposing equals false, the method has been called by the 
      ' runtime from inside the finalizer and you should not reference 
      ' other objects. Only unmanaged resources can be disposed.
      Protected Overridable Overloads Sub Dispose(ByVal disposing As Boolean)
         ' Check to see if Dispose has already been called.
         If Not Me.disposed Then
            ' If disposing equals true, dispose all managed 
            ' and unmanaged resources.
            If disposing Then
               ' Dispose managed resources.
               component.Dispose()
            End If

            ' Call the appropriate methods to clean up 
            ' unmanaged resources here.
            ' If disposing is false, 
            ' only the following code is executed.
            CloseHandle(handle)
            handle = IntPtr.Zero

            ' Note disposing has been done.
            disposed = True

         End If
      End Sub

      ' Use interop to call the method necessary  
      ' to clean up the unmanaged resource.
      <System.Runtime.InteropServices.DllImport("Kernel32")> _
      Private Shared Function CloseHandle(ByVal handle As IntPtr) As [Boolean]
      End Function

      ' This finalizer will run only if the Dispose method 
      ' does not get called.
      ' It gives your base class the opportunity to finalize.
      ' Do not provide finalize methods in types derived from this class.
      Protected Overrides Sub Finalize()
         ' Do not re-create Dispose clean-up code here.
         ' Calling Dispose(false) is optimal in terms of
         ' readability and maintainability.
         Dispose(False)
         MyBase.Finalize()
      End Sub
   End Class

   Public Shared Sub Main()
      ' Insert code here to create
      ' and use the MyResource object.
   End Sub

End Class

(更新)[检查]

如果您的子表单有签名。这些默认情况下会添加到表单中。

'Form overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
    MyBase.Dispose(disposing)
End Sub

@AmRan。我终于按照你的指示去做了,我发现MyResource类唯一涉及的时间是在FormLoad而不是FormClosed。当然,我已经将所有Using引用放在了这个子FormLoad中。现在我需要你的帮助,因为内存没有被释放。 - Lefteris Gkinis
此代码旨在供实现使用非托管资源的自定义类的开发人员使用。由于您不是重新发明整个Windows Forms库并创建自己的“Form”对象,因此极其不可能需要任何此代码混乱。这与原始问题完全无关,并且忽略了管理和非托管资源处理之间的根本差异。更糟糕的是,它忽略了原始投诉,即内存使用量似乎没有减少。这不会改变这一点。有关详细信息,请参见我的答案。 - Cody Gray
我并不是在说你的回答是“错误”的。我的意思是它“没有帮助”,这与向下箭头的工具提示很相似。它并不适用于这个问题,也没有回答所提出的问题,并且几乎有100%的概率把提问者引入完全错误的方向。 - Cody Gray
@AmRan。回答Cody的问题,我想说目前我没有使用任何间谍软件。此外,我发现每次关闭子窗体时,我都会释放“StatusBar”,但我没有释放窗体本身。现在我已经做到了,但是内存仍然没有被清理干净。现在我将把您的更新放入我的代码中,看看会发生什么。 - Lefteris Gkinis
@AmRan。别忘了问题是在本地网络PC中发现的,而不是我的PC上有SQL Server。我的代码中有四个自定义类,一个处理变量,另一个处理DB访问过程和函数,另一个处理报告等。 - Lefteris Gkinis
显示剩余4条评论

1

您可以使用其中一个可用的内存分析器,例如ANTS Memory Profiler(请参阅在WinForms应用程序中使用ANTS Memory Profiler跟踪内存泄漏)。您也可以使用WinDbg,但如果没有经验,则比专业工具更复杂。

常见的“内存泄漏”原因之一是将“外部”事件处理程序添加到窗体(例如静态或长期存在的对象)中,并在窗体销毁时不予删除,因此GC“认为”您对窗体有引用并且不收集其数据。

为什么以及如何避免事件处理程序内存泄漏?

.NET内存泄漏案例研究:导致内存膨胀的事件处理程序

垃圾回收基础

处理、终结和资源管理

如何在公共语言运行时中识别内存泄漏

如何检测和避免.NET应用程序中的内存和资源泄漏


@Nick,请为我解释一下"EXTERNAL事件处理程序"的含义。因为我的所有事件处理程序都像往常一样在程序内部。 - Lefteris Gkinis
@Lefteris Gkinis,在表单对象之外(另一个应用程序内的另一个对象的方法)参见避免事件处理程序内存泄漏的原因和方法 - http://bit.ly/hkWf9z,Jon Skeet的答案和讨论。 - Nick Martyshchenko
1
@Nick:请不要在Stack Overflow上使用混淆链接。评论格式化将自动缩短过长的URL以方便阅读,但是展示我们被链接到的内容非常重要。你有600个字符可以使用;这不是Twitter。 - Cody Gray
@Cody Gray,好的。发生这种情况有很多原因,我会这样做,但如果不方便,我会停止缩短它们。 - Nick Martyshchenko
@Nick:我不太明白你的意思。你是怎么想到先通过混淆服务运行URL会更方便呢?直接从地址栏复制粘贴即可。即使你有一个浏览器扩展程序可以轻松完成这个操作,我仍然无法想象它会更简单。无论如何,如果你能使用完整的地址,我会很感激的。谢谢。 - Cody Gray
@Cody Gray,我将http://bit.ly视为具有扩展统计信息的缩短服务,而不是混淆服务。我在Chrome中安装了插件,可以在需要时自动单击转换所有链接,并包括页面标题。非常方便。当您悬停在缩短链接上时,它还会显示完整链接的提示。我不喜欢没有标题的链接,因此在手动操作时必须再进行一次复制和粘贴。我也很想知道有多少人真正点击了我的链接。而且我并不是很喜欢SO如何缩短链接。无论如何,我停止使用它,因为这对其他人来说并不方便。 - Nick Martyshchenko

0

垃圾回收器会为你处理这些工作,所以你真的不需要担心它。只有在使用非托管资源(com interop/PInvoke)时,你才需要更深入地了解其中的情况。


我注意到当内存没有被释放时,我会丢失变量。我的意思是,在我的工作中和在最初的输入中,我的变量都是正常的,但是经过一段时间后,我会失去我的变量值,所以我决定自己释放内存,如果我可以的话。 - Lefteris Gkinis
@Lefterins:你可以这么做,但最好不要:因为有垃圾回收器被设计出来以最高效的方式来完成此任务。 - Felice Pollano
是的,我使用非托管资源。请帮助我解决这个问题。如何释放资源。 - Lefteris Gkinis

0

我不是VB.net的专家,但据我所知它有一个垃圾回收器。这通常意味着关闭子窗口并不会释放内存,但如果您删除所有对子窗口的引用,垃圾回收器可能会在下一次运行时释放它。

另外,通常可以“请求”垃圾回收器运行,但它通常会自行“决定”何时运行。


内存不会被垃圾回收器自动释放,而是不断增加。 - Lefteris Gkinis
我不了解 .net 垃圾回收器,但如果它与 Java 相似,它将为新分配的空间释放空间,但通常该内存直到程序结束才会返回到操作系统。如果垃圾回收器定期运行,则即使频繁打开和关闭(即创建和处理)子窗口,内存消耗也应最终稳定在某个水平。 - Thomas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接