【问题标题】:GetRef's memory consumption (garbage collection) changed with KB4525236GetRef 的内存消耗(垃圾收集)随 KB4525236 更改
【发布时间】:2020-02-06 07:28:05
【问题描述】:

在我们的 Windows 2016 服务器/Windows 10 客户端上安装 KB4525236 后,我们遇到了内存不足的问题。这个安全修复似乎改变了通过GetRef调用函数时内存被垃圾收集的时刻。

前 KB4525236

在通过GetRef 调用的函数中创建的每个实例都会在实例变量设置为nothing 后立即被垃圾回收

发布 KB4525236

在通过GetRef 调用的函数中创建的每个实例都保留在内存中,并且只有在整个函数完成时才会收集垃圾。在循环中创建实例时,这会很快累加并导致内存不足,尤其是在 32 位进程中。

问题

  • 我们在网上找不到任何相关内容,因此我们希望得到其他遇到相同问题的人的确认。
    编辑划掉:this 是同样的问题,但没有解决方案尚未
    (自 KB4524570(2019 年 11 月 12 日)Windows 10 1903 以来的 vbscript.dll class_terminate 错误)
  • 如果有人能够验证并知道可行的解决方案,那就太棒了。

POC

在安装了 KB4525236 的设备上运行的以下脚本显示了垃圾收集的差异

  • 直接调用:第二个实例仅在第一个实例被销毁后创建(这是我们想要的行为)
  • 通过GetRef 调用:第二个实例被创建之前第一个实例被销毁,因此有两个实例使用内存。

另存为:KB4525236.vbs
运行为:wscript KB4525236.vbs

Dim Name, Log

Class IDummyInstance
  Dim FName
  Sub Class_Initialize
    FName = Name
    Log = Log & "Initialize " & FName & VbNewLine
  End Sub
  Sub Class_Terminate
    Log = Log & "Terminate " & FName & vbNewLine
  End Sub
End Class

Sub CreateDestroyTwoInstances
  Dim DummyInstance
  Name = "First Instance"
  Set DummyInstance = New IDummyInstance
  Set DummyInstance = Nothing
  Name = "Second Instance"
  Set DummyInstance = New IDummyInstance
  Set DummyInstance = Nothing
End Sub

Log = "(1) Direct Call :" & VbNewLine
Call CreateDestroyTwoInstances

Log = VbNewLine & Log & "(2) GetRef Call :" & vbNewLine
Set GetRefCall = GetRef ("CreateDestroyTwoInstances")
Call GetRefCall

MsgBox Log

【问题讨论】:

  • @Lankymart - 问题是在GetRef() 中创建的实例在GetRef() 结束之前不会被垃圾收集。那和原来的不一样。我们有通过 GetRef() 调用的函数创建 1000 个实例,它们会不断累积内存,直到 GetRef() 结束,而在过去,它们在 GetRef() 中执行循环时被释放。
  • 感谢您的澄清,我不确定您将能够对此做些什么。想象一下,如果有人知道,那将是 @eric-lippert,因为他们在构建 VBScript 的原始团队中工作。
  • 我有你在没有 KB4525236 或 KB4524570 的 Windows 7 上描述的行为(显然还有另一个 KB 对 Windows 7 执行此操作)。尽管如此,VBScript 中没有垃圾回收,当对象must 的引用计数降至零时将被销毁。如果这没有发生,那就是引擎错误,而不是 GC 运行的不同方式。
  • 即使没有显式变量也是如此。两个With New IDummyInstance : End With 块仍然产生“初始化第一个实例,初始化第二个实例,终止第一个实例,终止第二个实例”。这是非常错误的,应该举报。除了内存消耗之外,它完全打破了this
  • @Gserg - 谢谢。至少,this 让我开心;)

标签: memory-management vbscript null garbage-collection reference-type


【解决方案1】:

由于我没有解释该问题的解决方案或官方消息来源,我一直在等待赏金到期。

我想出了一个令人不快的解决方法,它可以在错误得到修复之前提供帮助。

解决方法是不要使用任何局部变量来保存可能通过GetRef 执行的过程中的对象实例。

使用本地(或全局,如果没有递归)字典对象来保存对象实例并通过该字典调用它们,而不是隐式或显式变量。

Sub CreateDestroyTwoInstances
  Dim Refs
  Set Refs = CreateObject("Scripting.Dictionary")
  Name = "First Instance"
  Refs.Add "DummyInstance", New IDummyInstance
  ' Call Refs("DummyInstance").DoSomething()
  Refs.Remove "DummyInstance"
  Name = "Second Instance"
  Refs.Add "DummyInstance", New IDummyInstance
  ' Call Refs("DummyInstance").DoSomething()
  Refs.Remove "DummyInstance"
End Sub

如果你有一个不太复杂的脚本,它似乎值得使用。

【讨论】:

  • 刚刚测试过,我可以确认它可以在我的机器上运行。我将把它标记为解决方案。在微软提供修复之前这是最好的(假设他们承认这是一个错误)
  • 请注意Scripting.Dictionary 非常慢!
  • @AlexLaforge 当然是,无论如何这是一种解决方法。该错误已由 Microsoft 修复,无需再使用 Scripting.Dictionary 进行此类工作。
  • 遗憾的是,该错误已在 Windows 10 和 Server 版本中修复,但在以前的版本中未修复(例如 Windows Server 2012 R2)。我可以确认 Win 2012 R2 仍然受到影响,即使在撰写此评论时已完全更新。
  • @AlexLaforge 真的吗?我不知道,因为我们的整个堆栈都是在 2016 年构建的。
【解决方案2】:

很好的问题。我是通过 Microsoft 论坛帖子找到它的

https://answers.microsoft.com/en-us/windows/forum/all/vbscriptdll-classterminate-bug-since-kb4524570/1b34d9b4-91ce-4d61-a05c-1bfa0ec96344?auth=1

这个 SO 的提问者添加了一个指向上面 Microsoft 线程的链接。

在 KB5005101 的发行说明中引用了该 Microsoft 线程(对于 Windows 10 21H1 - 其他 Windows 10 版本将有类似的 KB)。 微软发行说明链接:https://support.microsoft.com/en-us/topic/september-1-2021-kb5005101-os-builds-19041-1202-19042-1202-and-19043-1202-preview-82a50f27-a56f-4212-96ce-1554e8058dc1

Microsoft 声称已解决此问题 Addresses a memory leak that occurs when you use nested classes within VBScript. 在发行说明中,末尾的 VBScript 是指向该 Microsoft 论坛主题的超链接。相当微妙...

无论如何,虽然我不是 VBScript 的频繁用户,但作为一个长期的 COM 用户,以及对这类问题感兴趣的读者,我想我会分享最终应该是最终答案的答案他们每月的补丁。这些发行说明只能追溯到 Windows 10 2004,因此 1909 及更早版本可能不走运,受此影响的 Windows 7 用户也可能如此。

【讨论】:

    【解决方案3】:

    有一种迂回的方法可以让 VBScript '现在'而不是以后终止对象。

    通过将以下观察结果拼凑在一起:

    • 如果您调用类实例的成员函数,则对象“清理”将推迟到函数末尾。

    • 如果通过引用传递参数,则可以修改值(例如设置为 Nothing)。

    你可能会得到这样的结果:

    Class Disposal
       Public Sub Dispose(ByRef oRef)
          Set oRef = Nothing
       End Sub
    End Class
    
    ' A global instance ready for use.
    Dim GD: Set GD = New Disposal
    

    要使用处置,您需要替换:

    Set x = Nothing
    

    与:

    GD.Dispose x
    

    下面是对将对象传递给“dispose of”时发生的情况的粗略解释:

    • Dispose 引用一个对象并将其设置为 Nothing(不言自明)。
    • 在函数结束时,VBScript“看到”不再有对该对象的引用。
    • VBScript 终止对象。

    或者至少,这就是我认为它正在做的事情......

    以下是应用于 POC 的解决方案:

    Class Disposal
       Public Sub Dispose(ByRef oRef)
          Set oRef = Nothing
       End Sub
    End Class
    
    ' A global instance ready for use.
    Dim GD: Set GD = New Disposal
    
    Dim Name, Log
    
    Class IDummyInstance
      Dim FName
      Sub Class_Initialize
        FName = Name
        Log = Log & "Initialize " & FName & VbNewLine
      End Sub
      Sub Class_Terminate
        Log = Log & "Terminate " & FName & vbNewLine
      End Sub
    End Class
    
    Sub CreateDestroyTwoInstances
      Dim DummyInstance
      Name = "First Instance"
      Set DummyInstance = New IDummyInstance
      GD.Dispose DummyInstance 'Set DummyInstance = Nothing
      Name = "Second Instance"
      Set DummyInstance = New IDummyInstance
      GD.Dispose DummyInstance 'Set DummyInstance = Nothing
    End Sub
    
    Log = "(1) Direct Call :" & VbNewLine
    Call CreateDestroyTwoInstances
    
    Log = VbNewLine & Log & "(2) GetRef Call :" & vbNewLine
    Set GetRefCall = GetRef ("CreateDestroyTwoInstances")
    Call GetRefCall
    
    MsgBox Log
    

    希望这能帮助那些在 2021 年及以后仍坚持使用 VBScript 的人...

    【讨论】:

    • 太棒了!我很久以前创建了一个Sub Dispose(byRef variable)。它是Sub,而不是Class。我是否可以偶然认为它的工作方式与您的 Class 示例相同?
    • 跟进我之前的评论:使用Sub Dispose(byRef variable) 确实 NOT 解决了这个问题。所以这里提出的Class 解决方法IS 是要走的路! (测试成功)
    猜你喜欢
    • 2011-04-25
    • 2019-07-06
    • 1970-01-01
    • 1970-01-01
    • 2012-06-12
    • 2018-05-10
    • 1970-01-01
    • 1970-01-01
    • 2017-03-15
    相关资源
    最近更新 更多