从ESXi读取VM时遭遇System.OutOfMemoryException问题求助
问题:从ESXi读取VM信息时频繁出现System.OutOfMemoryException错误
我在尝试使用以下C#函数从ESXi虚拟机管理程序读取VM信息时遇到了问题:
public static ReadHypervisorVmListRspn ReadHypervisorVmList(ReadHypervisorVmListRqst rqst) { PowerShellClient ps = null; try { ps = new PowerShellClient(); var rspn0 = ps.ExecuteScript("Set-ExecutionPolicy -Scope CurrentUser Unrestricted"); var rspn = new PowerShellClient.ExecuteRspn(); // Login to the hypervisor var cmd = "Connect-VIServer -Server " + rqst.LoginInfo.HostUrl + " -Protocol https -User " + rqst.LoginInfo.HostUserName + " -Password " + rqst.LoginInfo.HostPassword; rspn = ps.ExecuteScript(cmd); if (rspn.Response != "OK") { return new ReadHypervisorVmListRspn { Response = "Unable to connect !!" }; } // Fetch VM information cmd = "Get-Vm | Select-Object Name,PowerState,NumCPU,MemoryGB,UsedSpaceGB,ProvisionedSpaceGB,@{N='GuestFullName';E={@($_.guest.OSFullName)}},@{N='PrivateIp';E={@($_ | Get-View).Guest.Ipaddress}},@{N='MacAddress';E={($_ | Get-NetworkAdapter).MacAddress}},@{N='Uuid'; E={($_ |Get-View).config.uuid}},@{N='UpTime';E={($_ |Get-View).Summary.QuickStats.Uptimeseconds}},@{n='HardDiskSizeGB'; e={(Get-HardDisk -VM $_ | Measure-Object -Sum CapacityGB).Sum}}"; var rspn1 = ps.ExecuteScript(cmd, true, 1); if (rspn1.Response.Contains("An item with the same key has already")) rspn1 = ps.ExecuteScript(cmd); if (rspn1.Response != "OK") { // Logout from the hypervisor cmd = "Disconnect-VIServer -Confirm:$false;"; var rspn5 = ps.ExecuteScript(cmd); return new ReadHypervisorVmListRspn { Response = rspn1.Response }; } // Logout from the hypervisor cmd = "Disconnect-VIServer -Confirm:$false;"; var rspn4 = ps.ExecuteScript(cmd); // Process fetched VM information var rspn3 = rspn1.Output; var VmInfo = Json4.GetType<List<VmInfo>>(rspn3); var rspn2 = new ReadHypervisorVmListRspn() { Response = "OK", VmInfo = VmInfo }; return rspn2; } catch (Exception ex) { return new ReadHypervisorVmListRspn { Response = "Error" }; } finally { // Clean up resources ps = null; GC.Collect(); GC.WaitForPendingFinalizers(); } }
该函数有时能成功运行,但多数情况下会出现System.OutOfMemoryException错误,错误信息如下:
18-02-2024 16:14:19 Get-NetworkAdapter Exception of type 'System.OutOfMemoryException' was thrown. 18-02-2024 16:14:20 Get-NetworkAdapter Exception of type 'System.OutOfMemoryException' was thrown. 18-02-2024 16:14:20 Get-HardDisk Exception of type 'System.OutOfMemoryException' was thrown. 18-02-2024 16:14:21 Get-View Exception of type 'System.OutOfMemoryException' was thrown.
我已尝试通过循环分批读取VM,但问题仍未解决,请问有什么可行的修复方法吗?
修复建议
1. 优化PowerShell脚本,减少重复资源调用
原脚本对每个VM多次调用Get-View、Get-NetworkAdapter、Get-HardDisk,会重复创建对象并持续占用内存。可以一次性获取所有VM的视图数据,直接从视图中提取所需信息,避免重复查询:
# 一次性加载所有VM的核心属性,避免多次子命令调用 Get-VM | Get-View -Property Name, Runtime.PowerState, Config.Hardware.NumCPU, Config.Hardware.MemoryMB, Guest.OSFullName, Guest.IpAddress, Config.Uuid, Summary.QuickStats.UptimeSeconds, Config.Hardware.Device | Select-Object ` @{N='Name';E={$_.Name}}, @{N='PowerState';E={$_.Runtime.PowerState}}, @{N='NumCPU';E={$_.Config.Hardware.NumCPU}}, @{N='MemoryGB';E={[math]::Round($_.Config.Hardware.MemoryMB/1024,2)}}, @{N='UsedSpaceGB';E={[math]::Round((Get-VM -Id $_.MoRef).UsedSpaceGB,2)}}, @{N='ProvisionedSpaceGB';E={[math]::Round((Get-VM -Id $_.MoRef).ProvisionedSpaceGB,2)}}, @{N='GuestFullName';E={$_.Guest.OSFullName}}, @{N='PrivateIp';E={$_.Guest.IpAddress -join ';'}}, @{N='MacAddress';E={($_.Config.Hardware.Device | Where-Object {$_.GetType().Name -eq 'VirtualEthernetCard'}).MacAddress -join ';'}}, @{N='Uuid';E={$_.Config.Uuid}}, @{N='UpTime';E={$_.Summary.QuickStats.UptimeSeconds}}, @{N='HardDiskSizeGB';E={[math]::Round((($_.Config.Hardware.Device | Where-Object {$_.GetType().Name -match 'VirtualDisk'}).CapacityInBytes | Measure-Object -Sum).Sum/1GB,2)}}
2. 改进PowerShell客户端资源管理
- 移除手动调用
GC.Collect()的代码,手动触发垃圾回收会打乱系统自动回收机制,反而可能加剧内存问题。 - 确保
PowerShellClient实现IDisposable接口,在finally块中调用ps.Dispose()释放资源,而非仅将对象设为null。 - 检查
PowerShellClient的ExecuteScript方法实现,确保每次执行后清理PowerShell运行空间,避免会话中累积未释放的对象。
3. 优化分批处理的资源释放逻辑
如果坚持分批读取,需确保每批处理完成后立即释放当前批次的输出对象,避免内存堆积:
// 示例:按批次处理VM var batchSize = 10; var vmNamesOutput = ps.ExecuteScript("Get-VM | Select-Object -ExpandProperty Name", true, 1).Output; var vmNameList = Json4.GetType<List<string>>(vmNamesOutput); foreach (var batch in vmNameList.Chunk(batchSize)) { var quotedNames = string.Join(',', batch.Select(n => $"'{n}'")); // 使用优化后的PowerShell查询脚本 var cmd = $"Get-VM -Name {quotedNames} | Get-View -Property ... | Select-Object ..."; var batchRspn = ps.ExecuteScript(cmd, true, 1); // 处理当前批次数据 var batchVmInfo = Json4.GetType<List<VmInfo>>(batchRspn.Output); VmInfo.AddRange(batchVmInfo); // 显式清理当前批次资源 batchRspn = null; }
4. 避免一次性加载大体积JSON
如果VM数量较多,一次性返回所有VM的JSON字符串会占用大量内存。可以让PowerShell脚本逐行输出单条VM的JSON数据,在C#端流式读取并处理,而非一次性加载整个JSON到内存。
5. 移除重复的执行策略设置
Set-ExecutionPolicy -Scope CurrentUser Unrestricted只需执行一次,无需每次调用函数都重复执行。可以在应用启动时检查并设置执行策略,减少不必要的操作开销。
内容的提问来源于stack exchange,提问作者Maya
相关产品推荐
相关产品推荐

