Powershell cmdlet Get-WinEvent 导致内存泄漏

问题描述 投票:0回答:1

我正在编写一个简单的 Powershell 脚本来将 Windows EventLogs 转发到外部 Syslog 服务器 - 该脚本应该作为服务持续运行。为了从 EventLog 中获取事件,我在循环中使用

Get-EventLog
cmdlet,并具有适当的睡眠间隔(例如一秒)。 看来,在循环中运行
Get-EventLog
会导致内存泄漏,因为 cmdlet 使用的内存在不再使用后不会被释放。每次 cmdlet 调用后,脚本都会消耗越来越多的内存。 以下是导致问题的 Powershell 代码示例:

$interval = 0 # 0 seconds to make the issue more apparent
$channel = "System"

while ($true){
    Get-WinEvent -LogName $channel -MaxEvents 1 | Out-Null
    Start-Sleep -Seconds $interval
}

我尝试了以下方法来修复它 - 但没有任何效果。

  1. 将 cmdlet 的结果存储在变量中并使用
    .Dispose()
  2. 进行处理
while ($true){
    $event = Get-WinEvent -LogName $channel -MaxEvents 1
    $event.Dispose()
    Start-Sleep -Seconds $interval
}
  1. 将 cmdlet 的结果存储在变量中并使用
    Remove-Variable
    将其删除。
while ($true){
    $event = Get-WinEvent -LogName $channel -MaxEvents 1
    Remove-Variable -Name event
    Start-Sleep -Seconds $interval
}
  1. 强制运行垃圾收集器。
while ($true){
    Get-WinEvent -LogName $channel -MaxEvents 1 | Out-Null
    [System.GC]::Collect()
    Start-Sleep -Seconds $interval
}

有什么方法可以解决脚本中的这个问题,还是 cmdlet 本身的问题?

编辑1 我尝试了@mclayton 提出的建议 - 没有用。

while ($true){
    $event = Get-WinEvent -LogName $channel -MaxEvents 1 | Out-Null
    if ($event -is [IDisposable]) {$event.Dispose()};
    [System.GC]::WaitForPendingFinalizers();
    [System.GC]::Collect()
    Start-Sleep -Seconds $interval
}
powershell memory-leaks
1个回答
0
投票

据我所知,存在没有内存泄漏

  • Get-WinEvent
    cmdlet(通常)输出
    System.Diagnostics.Eventing.Reader.EventLogRecord
    类型的实例,它(间接)实现
    System.IDisposable
    接口:

      使用
    • 非托管

      资源(即由.NET运行时管理的资源)的.NET类型实现此接口,以便允许按需释放此类资源,理想情况下通过调用接口的

      .Dispose()
      方法一旦不再需要给定的 .NET 类型的实例。 (C# 等 .NET 语言为此提供了语法糖,即 using (...) { ... }
       模式,但 PowerShell 没有)。

    • 但是,

      IDisposable

      实现者应确保通过
      终结器(在.NET垃圾收集器回收给定对象之后被称为最终释放非托管资源。

  • 因此:
    • 与任何 .NET 对象一样,它占用的内存只会根据
    • 内存压力

      定期(以未指定间隔)或按需回收。

    • 但是,您可以按需
    • 调用垃圾收集器

      - 总是以同步(阻塞)方式 - 使用 [System.GC]::Collect()

      
      
      

      类似地,您可以
        afterwards
      • 调用[System.GC]::WaitForPendingFinalizers()
        ,以便
        also
        同步等待垃圾收集对象的终结器运行。
由上可知:

    如果您想尽快释放非托管资源,请在实现
  • .Dispose()

    的对象上调用

    IDisposable
    
    

    这本身独立于并且不
      保证调用该方法的 .NET 实例的垃圾收集:垃圾收集完全基于给定实例是否仍被其他运行时对象
    • 引用
  • 强制
  • 不再引用的对象进行垃圾回收 - 以始终同步且因此

    阻塞的方式 - 调用 [GC]::Collect()

      IDispose
    • 接口的正确实现者

      最终

      还将通过
      终结器释放垃圾收集对象所持有的任何非托管资源。

    • 等待非托管资源的释放,请致电

      [GC]::WaitForPendingFinalizers() afterwards

  • 以下
示例代码
演示了

不存在内存泄漏:

它立即同步垃圾收集
    Get-WinEvent
  • 输出对象,并等待运行它们的终结器。

    它报告当前进程使用的内存量 - 通过正在运行的进程的
  • .WorkingSet64

    属性 - 在每 N 个(可配置)调用之后。

    
    

    您应该看到,从长远来看,内存使用量确实
  • 不会
  • 无限期地上升。

      看到波动(.NET内存管理的方式很神秘),但重要的是
    • 内存使用量不会持续增长
    # Helper function for printing the current memory usage (working set) function Get-MemoryUsage { # Perform garbage collection before the first call. if ($i -eq 1) { [GC]::Collect(); [GC]::WaitForPendingFinalizers() } $thisProcess.Refresh() # Get the latest memory stats. [pscustomobject] @{ CallCount = $i - 1 'Memory Use (Working Set, MB)' = '{0:N2}' -f ($thisProcess.WorkingSet64 / 1mb) } } $thisProcess = Get-Process -Id $PID # get a process-info object for this process $channel = 'System' # the event log to query $sleepIntervalMsecs = 0 # msecs. to sleep between calls. $memoryReportInterval = 500 # report memory usage every N calls # Enter an infinite loop; press Ctrl-C to abort. $i = 0 while ($true) { # Report the memory usage at the start and after every N objects. # Note: Because of how implicitly table-formatted output behaves, the # first memory snapshot won't display until the second one is available. if (++$i % $memoryReportInterval -eq 1) { Get-MemoryUsage } # Discard the object via a $null = ... assignment. # Its disposal won't be triggered until after the discarded object is garbage-collected. $null = Get-WinEvent -LogName $channel -MaxEvents 1 # Garbage-collect now, then wait for finalizers to run, which takes care of disposal. [GC]::Collect(); [GC]::WaitForPendingFinalizers() # Sleep before the next call. Start-Sleep -Milliseconds $sleepIntervalMsecs }
这是在 Windows 11 (22H2) 上的
Windows PowerShell
中运行上述内容的

示例输出,显示内存使用量没有长期增长: CallCount Memory Use (Working Set, MB) --------- ---------------------------- 0 83.23 500 85.82 1000 85.83 1500 84.86 2000 84.79 2500 85.05 3000 84.84 3500 84.88 4000 84.87 4500 84.91 5000 84.98 5500 84.95 6000 84.90 6500 84.91 7000 84.89 7500 84.88 8000 84.93 8500 84.95 9000 84.88 9500 84.98 10000 84.93 10500 84.90 11000 84.88 11500 84.98 12000 84.88 12500 84.93 13000 84.89 13500 85.04 14000 84.93 14500 84.99 15000 84.89 15500 84.86 16000 84.94 16500 84.96 17000 84.93 17500 84.95 18000 84.89 18500 84.89 19000 84.86 19500 84.89 20000 84.84 20500 84.89


© www.soinside.com 2019 - 2024. All rights reserved.