我正在编写一个简单的 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
}
我尝试了以下方法来修复它 - 但没有任何效果。
.Dispose()
while ($true){
$event = Get-WinEvent -LogName $channel -MaxEvents 1
$event.Dispose()
Start-Sleep -Seconds $interval
}
Remove-Variable
将其删除。while ($true){
$event = Get-WinEvent -LogName $channel -MaxEvents 1
Remove-Variable -Name event
Start-Sleep -Seconds $interval
}
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
}
据我所知,存在没有内存泄漏:
Get-WinEvent
cmdlet(通常)输出System.Diagnostics.Eventing.Reader.EventLogRecord
类型的实例,它(间接)实现System.IDisposable
接口:
资源(即不由.NET运行时管理的资源)的.NET类型实现此接口,以便允许按需释放此类资源,理想情况下通过调用接口的
.Dispose()
方法一旦不再需要给定的 .NET 类型的实例。 (C# 等 .NET 语言为此提供了语法糖,即 using (...) { ... }
模式,但 PowerShell 没有)。
IDisposable
实现者应确保通过终结器(在.NET垃圾收集器回收给定对象之后被称为)最终释放非托管资源。
定期(以未指定间隔)或按需回收。
- 总是以同步(阻塞)方式 - 使用 [System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
,以便also同步等待垃圾收集对象的终结器运行。
.Dispose()
的对象上调用
IDisposable
。
这本身独立于并且不阻塞的方式 - 调用 [GC]::Collect()
IDispose
最终
还将通过终结器释放垃圾收集对象所持有的任何非托管资源。 要
[GC]::WaitForPendingFinalizers()
afterwards
。
不存在内存泄漏:
它立即同步垃圾收集Get-WinEvent
它报告当前进程使用的内存量 - 通过正在运行的进程的
.WorkingSet64
您应该看到,从长远来看,内存使用量确实你
会# 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 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