【问题标题】:Build class that implement a memory-leak check构建实现内存泄漏检查的类
【发布时间】:2019-06-14 11:21:55
【问题描述】:

我想在我的应用程序中找到内存泄漏。 我会尝试在不安装软件或扩展程序的情况下查找泄漏。

点击此链接

https://michaelscodingspot.com/5-techniques-to-avoid-memory-leaks-by-events-in-c-net-you-should-know/

我尝试建立自己的课程。

代码如下:

''' <summary>
''' Use: lanch instance of the class
''' - Friend memorytest As New MemoryLeakTest(MemoryLeakTest.memorySize.MBytes)
''' Parameters:
''' - MemorySize (MBytes, KBytes, Bytes)
''' - AlertSize (set Alert to True if the increment of the new memory usage it's >)
''' 
''' And then, to record the memory usage of a step
''' - memorytest.NewStep("abc")
''' 
''' To record and get the memory usage of every step:
''' - PrintMethod(memorytest.NewStep("abc").ToString)
''' 
''' To record a step and get only results with an Increment of the Memory used > initial alertSize setting:
''' - If memoryTest.NewStepConditioned("abc") Then PrintMethod(memoryTest.LastStep)
''' 
''' To get the sum of all Memory increments by step:
''' - PrintMethod(memorytest.GetMemoryUsedByStep("abc"))
''' </summary>
''' <remarks></remarks>
Friend Class MemoryLeakTest
    Sub New(Optional ByVal newMemorySize As memorySize = memorySize.KBytes, Optional ByVal newAlertSize As Integer = 0)
        _memorySize = newMemorySize
        _alertSize = newAlertSize
    End Sub

    Private _memorySize As memorySize
    Private _alertSize As Integer
    Private _Priority As Integer
    Private _Id As Integer
    Private _Title As String
    Private _MaxWorkingSet As IntPtr
    Private _MinWorkingSet As IntPtr
    Private _ProcessName As String
    Private _StartInfo As System.Diagnostics.ProcessStartInfo
    Private _PriorityClass As System.Diagnostics.ProcessPriorityClass

    Private _MemorySteps As New List(Of MemoryStep)

    Friend Function NewStep(ByVal _Step As String) As String
        CallGC()
        CheckMemoryStep(_Step)
        'CallGC()
        Return _MemorySteps(_MemorySteps.Count - 1).ToString
    End Function

    Friend Function NewStepConditioned(ByVal _Step As String) As Boolean
        CallGC()
        CheckMemoryStep(_Step)
        'CallGC()
        Return _MemorySteps(_MemorySteps.Count - 1).Alert
    End Function
    Friend Function LastStep() As String
        Return _MemorySteps(_MemorySteps.Count - 1).ToString
    End Function

    Friend Function GetMemoryUsedByStep(ByVal NewStep As String) As String
        Dim MemoryUsed As List(Of MemoryStep) = _MemorySteps.FindAll(Function(x) x._step = NewStep)
        Return MemoryUsed.Sum(Function(x) x._inc_WorkingSet).ToString & " " & _memorySize.ToString
    End Function

    Friend Sub CallGC()
        GC.Collect()
        GC.WaitForPendingFinalizers()
        'GC.Collect()
    End Sub
    Friend Sub CheckMemoryStep(ByVal newStep As String)
        Using cp As Process = Process.GetCurrentProcess
            If IsNothing(_StartInfo) Then
                _Priority = cp.BasePriority
                _Id = cp.Id
                _Title = cp.MainWindowTitle
                _MaxWorkingSet = cp.MaxWorkingSet
                _MinWorkingSet = cp.MinWorkingSet
                _PriorityClass = cp.PriorityClass
                _ProcessName = cp.ProcessName
                _StartInfo = cp.StartInfo
                _MemorySteps.Add(New MemoryStep(newStep, cp, _memorySize, _alertSize))
            Else
                _MemorySteps.Add(New MemoryStep(newStep, cp, _memorySize, _alertSize, _MemorySteps(_MemorySteps.Count - 1)))
            End If
        End Using
    End Sub

    Friend Class MemoryStep
        Sub New(ByVal newStep As String, ByVal cp As System.Diagnostics.Process, ByVal memSize As memorySize, ByVal alertSize As Integer, Optional ByVal oldStep As MemoryStep = Nothing)
            _memorySize = memSize
            _step = newStep
            _NonPagedSystemMemory = CLng(cp.NonpagedSystemMemorySize64 / memSize)
            _PagedMemory = CLng(cp.PagedMemorySize64 / memSize)
            _PagedSystemMemory = CLng(cp.PagedSystemMemorySize64 / memSize)
            _PeakPagedMemory = CLng(cp.PeakPagedMemorySize64 / memSize)
            _PeakVirtualMemory = CLng(cp.PeakVirtualMemorySize64 / memSize)
            _PeakWorkingSet = CLng(cp.PeakWorkingSet64 / memSize)
            _PrivateMemory = CLng(cp.PrivateMemorySize64 / memSize)
            _VirtualMemory = CLng(cp.VirtualMemorySize64 / memSize)
            _WorkingSet = CLng(cp.WorkingSet64 / memSize)

            If Not IsNothing(oldStep) Then
                With oldStep
                    _inc_NonPagedSystemMemory = _NonPagedSystemMemory - ._NonPagedSystemMemory
                    _inc_PagedMemory = _PagedMemory - ._PagedMemory
                    _inc_PagedSystemMemory = _PagedSystemMemory - ._PagedSystemMemory
                    _inc_PeakPagedMemory = _PeakPagedMemory - ._PeakPagedMemory
                    _inc_PeakVirtualMemory = _PeakVirtualMemory - ._inc_PeakVirtualMemory
                    _inc_PeakWorkingSet = _PeakWorkingSet - ._PeakWorkingSet
                    _inc_PrivateMemory = _PrivateMemory - ._PrivateMemory
                    _inc_VirtualMemory = _VirtualMemory - ._VirtualMemory
                    _inc_WorkingSet = _WorkingSet - ._WorkingSet
                End With
            End If

            If _inc_WorkingSet > alertSize Then
                Alert = True
            End If
        End Sub
        Private _NonPagedSystemMemory As Long
        Private _PagedMemory As Long
        Private _PagedSystemMemory As Long
        Private _PeakPagedMemory As Long
        Private _PeakVirtualMemory As Long
        Private _PeakWorkingSet As Long
        Private _PrivateMemory As Long
        Private _VirtualMemory As Long
        Private _WorkingSet As Long
        Private _memorySize As memorySize

        Private _inc_NonPagedSystemMemory As Long
        Private _inc_PagedMemory As Long
        Private _inc_PagedSystemMemory As Long
        Private _inc_PeakPagedMemory As Long
        Private _inc_PeakVirtualMemory As Long
        Private _inc_PeakWorkingSet As Long
        Private _inc_PrivateMemory As Long
        Private _inc_VirtualMemory As Long
        Friend _inc_WorkingSet As Long

        Friend _step As String
        Friend Alert As Boolean = False

        Public Overrides Function ToString() As String
            ToString = _step & vbTab & "memory usage: " & _WorkingSet.ToString & " " & _memorySize.ToString
            If _inc_WorkingSet < 0 Then
                ToString += " (- " & -_inc_WorkingSet.ToString & " " & _memorySize.ToString & ")"
            Else
                ToString += " (+ " & _inc_WorkingSet.ToString & " " & _memorySize.ToString & ")"
            End If
            Return ToString
        End Function
    End Class

    Enum memorySize
        MBytes = 1024000
        KBytes = 1024
        [Bytes] = 1
    End Enum
End Class

这个类的使用应该是:

Class Test
    Friend memorytest As New MemoryLeakTest(MemoryLeakTest.memorySize.MBytes)

    Sub RunTest()
        memorytest.NewStep("Start")

        Method_1()
        WriteToTextBox(memorytest.NewStep("Method_1").ToString)

        Method_2()
        WriteToTextBox(memorytest.NewStep("Method_2").ToString)

        Method_3()
        If memoryTest.NewStepConditioned("Method_3") Then WriteToTextBox(memoryTest.LastStep)
    End Sub

    Private Sub Method_1()
        'Do something
    End Sub

    Private Sub Method_2()
        'Do something
    End Sub

    Private Function Method_3()
        'Do something
    End Function

    Private Sub WriteToTextBox(ByVal msg As String)
        With TextBox1
            .SelectionStart = .Text.Length
            .SelectedText = VbCrlf & Date.Now.ToLongTimeString & Chr(9) & msg
        End With
    End Sub
End Class

在我看来效果很好,但是因为深入了解 GC 需要大量时间,而我目前没有,我会向社区询问这个类是否可以有效地用于查找内存泄漏,因为我构建了它.

【问题讨论】:

  • 内存泄漏是指对象在不打算保持活动状态的情况下仍保持活动状态的情况。但是没有人可以检查意图。如果有办法自动检测到,所有垃圾收集器都已经包含它,以释放所有打算释放的对象。
  • @Holger 谢谢,我测试并且代码有效。我提供的链接也解释了这是一种有效的方法。有时,当您有数千行代码时,可能是一种快速查找泄漏的方法,您不觉得吗?如果代码有效,您是否对其进行了测试?
  • 当我们只有“数千行代码”时,我们当然不需要内存分析工具。既然我们没有,我们肯定不会使用需要修改代码的工具。除此之外,不可能测试你的代码,因为你没有解释它应该做什么。显然,它应该记录内存使用情况并从中得出结论,这仅适用于非常简单的设置,而不是现实生活中的案例。
  • @Holger 感谢您的帮助。我不是专业的开发人员(可能你很快就认出了看代码),多年来,在学习的过程中,我在我的程序中添加了一些代码(在需要时也重写)......现在我的一些程序有大约 40.000 行代码。我从不关心内存泄漏,现在我意识到某个地方有很多泄漏。所以我正在努力解决所有问题。但老实说,我不喜欢购买、安装和研究分析器,所以我正在尝试手动了解泄漏(可能是事件)在哪里,我想用这段代码可能会很快......
  • 您的方法的问题是,它要求您知道哪些对象正在泄漏(或至少有怀疑)以及这些对象应该可回收的时间(断言内存消耗类似于分配之前的点)。但是,如果您已经知道这些事情,则不需要该工具。但是现实生活中的应用程序通常不是那么简单,因为对象的生命周期重叠,所以没有一点内存消耗必须与另一个时间点相同。而且您仍然不知道哪个引用阻止了对象的收集。

标签: vb.net memory-leaks garbage-collection profiler


【解决方案1】:

找到莫里泄漏并不容易。您应该重新考虑安装第 3 方软件来检测它们。或者在调试时使用 Visual Studio 诊断选项卡。

GC 很慢,因为它必须进行大量内存检查。这门课只会告诉你是否可能有一些泄漏。但它不会告诉你在哪里可以找到它们。

【讨论】:

  • 感谢您的回答,但您是否测试过代码是否有效?当您说 GS 它很慢时,您是指滴答声?小姐?因为 GC 的速度应该与机器、操作系统、空闲内存、程序优先级有关……很多东西,如docs.microsoft.com/en-us/dotnet/standard/garbage-collection/…
  • 关于在哪里可以找到,你为什么这么说?您放置的每个“步骤”,记录在该代码点中使用的内存。所以下一个会记录内存增量,下一个会记录GC没有释放的内存量。如果你注意到我在代码中调用了 GC.Collect 和 GC.WaitForPendingFinalizers
  • 你会得到很多假阳性结果。代码中使用的类可以构建所需的持久内部状态。这会增加使用的内存。
  • 尝试测量班级速度(使用秒表),看看它是否足够快。 GC.Collect() 和 GC.WaitForPendingFinalizers() 在常规使用中花费了太多时间。我建议您从 MemoryLeakTest 中删除所有不需要的代码。
【解决方案2】:

由于我没有收到很多答案,我自己回答。

我的问题是我有一个程序以 25 MB 的内存使用量开始,随着时间的推移它达到 1.5 GB,然后因“内存不足异常”而崩溃。

我有以下建议:

  • 我搜索了第 3 部分软件:但它需要大量时间来研究它的工作原理。
  • 安装了 Visual Studio Community 2019,它集成了 Visual Studio 诊断,但不提供即时准确的位置 泄漏。
  • 我在互联网上研究了有关内存泄漏的先前答案:我意识到通常泄漏是由于iDisposable 接口的实现不正确

通过我在示例中发布的课程,我轻松快速地找到了产生泄漏的过程。

在这些过程中,我确定了需要实现 iDisposable 的类,以及应该重写的代码部分。

那时我以这种模式解决了:

  • 正确实施 iDisposable:在这里找到一个简短的正确解释 Implementing IDisposable

  • 优化Class代码,使用不同的构造函数实例化实例。

特别是我替换了这种类型的代码

Class MyClass()
    Sub New()

    End Sub

    Friend Inst_of_my_2nd_class As New My2ndClass()
    Friend Inst_of_my_3rd_class As New My3rdClass()
    Friend Inst_of_my_4th_class As New My4thClass()
End Class

有了这个(注意。不太确定 Dispose 实例的代码,对我来说似乎效果很好)

Class MyClass()
    Implements iDisposable
    Sub New(ByVal ... As some)
        Inst_of_my_2nd_class = New My2ndClass()
    End Sub
    Sub New(ByVal ... As someother)
        Inst_of_my_3rd_class = New My3rdClass()
    End Sub
    Sub New(ByVal ... As someotherelse)
        Inst_of_my_4th_class = New My4thClass()
    End Sub

    Friend Inst_of_my_2nd_class As My2ndClass()
    Friend Inst_of_my_3rd_class As My3rdClass()
    Friend Inst_of_my_4th_class As My4thClass()

#Region "IDisposable Support"
    Protected Overridable Sub Dispose(disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: dispose managed state (managed objects).
                ' what to put here: thanks to https://stackoverflow.com/questions/19895856/implementing-idisposable/19896116#19896116
            End If

            ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
            ' what to put here: thanks to https://stackoverflow.com/questions/19895856/implementing-idisposable/19896116#19896116

            If Not IsNothing(Inst_of_my_2nd_class) Then
                Inst_of_my_2nd_class.Dispose()
                Inst_of_my_2nd_class = Nothing
            End If
            If Not IsNothing(Inst_of_my_3rd_class) Then
                Inst_of_my_3rd_class.Dispose()
                Inst_of_my_3rd_class = Nothing
            End If
            If Not IsNothing(Inst_of_my_4th_class) Then
                Inst_of_my_4th_class.Dispose()
                Inst_of_my_4th_class = Nothing
            End If

            ' TODO: set large fields to null.
            ' what to put here: thanks to https://stackoverflow.com/questions/19895856/implementing-idisposable/19896116#19896116

        End If
        Me.disposedValue = True
    End Sub

    ' TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources.
    'Protected Overrides Sub Finalize()
    '   ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
    '    Dispose(False)
    '    MyBase.Finalize()
    'End Sub

    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class

【讨论】:

    猜你喜欢
    • 2011-05-21
    • 1970-01-01
    • 1970-01-01
    • 2012-01-02
    • 2016-08-08
    • 2010-10-09
    • 1970-01-01
    • 1970-01-01
    • 2012-07-16
    相关资源
    最近更新 更多