VB.net垃圾收集器不释放对象

首先,提前感谢您的帮助.

我决定在这样的论坛上寻求帮助,因为经过几个月的努力,我无法找到解决问题的方法.

这可以被描述为“为什么即使在GC被强制启动时GC也不会释放在VB.net中创建的对象?”

请考虑以下代码.显然我的项目要复杂得多,但我能够找出问题所在:

Imports System.Data.Odbc
Imports System.Threading
Module Module1
    Sub Main()
        'Declarations-------------------------------------------------      
            Dim connex As OdbcConnection 'Connection to the DB
            Dim db_Str As String         'ODBC connection String      
        'Sentences----------------------------------------------------
            db_Str = "My ODBC connection String to my MySQL database"
            While True
                'Condition: Infinite loop.
                connex = New OdbcConnection(db_Str)
                connex.Open()
                connex.Close()

                'Release created objects
                connex.Dispose()

                'Force the GC to be launched
                GC.Collect()

                'Send the application to sleep half a second
                System.Threading.Thread.Sleep(500)
            End While
    End Sub
End Module

这模拟了一个多线程应用程序,它连接到MySQL数据库.如您所见,连接被创建为新对象,然后被释放.最后,GC被迫启动.我在几个论坛上看过这个算法,但也在MSDN在线帮助中看到过,所以就我而言,我没有做错任何事.

启动应用程序时会出现此问题.创建的对象位于代码中,但过了一段时间,可用内存耗尽,应用程序崩溃.

当然,在这个小版本中很难看到这个问题,但在实际项目中,应用程序会很快耗尽内存(由于一段时间内连接的数量),因此,正常运行时间仅为两天.然后我需要再次重启应用程序.

我在我的机器上安装了一个内存分析器(Scitech .Net Memory Profiler 4.5,可下载的试用版本here).有一个名为“调查内存泄漏”的部分.当我在“实时”标签上看到这个时,我感到非常惊讶.如果我是正确的,这个图形告诉我在代码上创建的对象都没有实际发布:

当我看到其他屏幕时,惊喜甚至更大.据此,所有未处理的对象都是System.Transactions类型,我假设它们是在.Net库中内部管理的,因为我没有在我的代码上创建这种类型的任何对象.这是否意味着VB.net标准库上有一个错误???:

请注意,在我的代码中,我没有执行任何查询.如果我这样做,ODBCDataReader对象也不会被释放,即使我调用.Close()方法(令人惊讶的是,此类型的未发布对象的数量与System.Transactions类型的未发布对象完全相同)

另一个重要的是语句GC.Collect().内存分析器使用它来刷新要显示的信息.如果您从代码中删除它,分析器将无法正确更新实时图表,给您错误的印象,即一切正确.

最后,如果你省略了connex.Open()语句,屏幕截图#1将呈现一条扁平线(这意味着所有创建的对象都已成功发布),但不幸的是,如果是,我们无法对数据库进行任何查询.连接尚未打开.

有人可以找到对此的逻辑解释,以及有效释放对象的解决方法吗?

谢谢所有人.

尼科

Dispose与垃圾收集无关.垃圾收集专门用于管理资源(内存). Dispose根本不影响内存,只与非托管资源(数据库连接,文件句柄,gdi资源,套接字……不是内存)有关.两者之间的唯一关系与对象的最终确定方式有关,因为许多对象经常被实现,因此处理它们将抑制最终化并最终确定它们将调用.Dispose().明确地Disposing()一个对象永远不会导致它被收集1.

明确地调用垃圾收集器几乎总是一个坏主意. .Net使用分代垃圾收集器,因此自己调用它的主要作用是你会更长时间地保留内存,因为通过强制收集更早,你可能会在它们有资格收集之前检查项目,这会将它们发送到较低阶段,而不是经常收集.否则这些项目将留在下一代,并且当GC下一次运行时,它们有资格收集.您现在可能需要为分析器使用GC.Collect(),但您应该尝试将其删除以用于生产代码.

你提到你的应用程序在崩溃之前运行了两天,并且没有对你的实际生产代码进行分析(或显示结果),所以我也认为分析器在某种程度上误导了你.你已经把代码削减到产生内存泄漏的东西,但我不确定这是你在生产中看到的内存泄漏.这部分是因为重现错误的时间不同,但它也是“本能”.我提到这一点,因为根据你的探查器结果,我要提出的一些建议可能没有意义.这样,我不确定你遗失的记忆是什么,但我可以做一些猜测.

第一个猜测是你的真实代码有try / catch块.抛出异常……可能不是每个连接都有,但有时候.当发生这种情况时,catch块允许程序继续运行,但是你跳过了connex.Dispose()行,因此保持打开的连接.这些连接最终将为数据库创建拒绝服务的情况,这可以通过多种方式表现出来.这里的修正是为了确保你总是使用finally块来阻止你.Dispose().无论你当前是否有try / catch块都是如此,重要的是我说你到目前为止发布的代码根本就是错误的:你需要一个try / finally.通过使用块有一个快捷方式.

下一个猜测是你的一些实际命令最终会相当大,可能涉及大字符串或图像(byte [])数据.在这种情况下,项目最终会产生一个称为大对象堆(LOH)的特殊垃圾收集器生成. LOH很少收集,几乎从未压实过.将压缩视为类似于对硬盘驱动器进行碎片整理时所发生的情况.如果您有物品进入LOH,您最终可能会释放(收集)物理内存本身,但您的进程中的地址空间(通常限制为2GB)不会被释放(压缩).您的内存地址空间中有漏洞无法回收.物理RAM可供您的系统用于其他进程,但随着时间的推移,这仍然会导致您看到的同一种OutOfMemory异常.大多数情况下这并不重要:大多数.Net程序是短暂的面向用户的应用程序,或ASP.Net应用程序,其中整个线程可以在页面提供后被拆除.既然你正在构建一个应该运行数天的服务,你必须要更加小心.修复可能涉及显着重新处理一些代码,以避免创建大对象.这可能意味着反复使用单个或一小组字节数组,或者使用流技术而不是字符串连接或字符串构建器来处理非常大的SQL查询或SQL查询数据.这也可能意味着您发现这更容易作为每日运行并在一天结束时自行关闭的计划任务,或者按需调用的程序.

最后的猜测是,你正在做的事情导致你的连接对象仍然可以通过你的程序以某种方式访问​​.事件处理程序是此类错误的常见来源,但我发现在您的连接上有事件处理程序很奇怪,特别是因为这不是您的示例的一部分.

1我想我可以设想一个能够实现这一目标的方案.一种简单的方法是构建一个对象,假定该类型的所有对象都有一个全局集合……对象在构造时将它们自己添加到集合中,并在处置时将它们自行移除.通过这种方式,在处理之前无法收集对象,因为在此之前它仍然可以访问……但这将是一个非常有缺陷的程序设计.

相关文章
相关标签/搜索