You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何在ForEach循环内使用Start-IISCommitDelay会导致PowerShell内存暴涨?

IISCommitDelay作用解析及循环调用内存激增原因

当在ForEach-Object的Process块中反复调用Start-IISCommitDelay和Stop-IISCommitDelay时,100次循环后PowerShell内存占用会剧增至1.5GB;若将这两个命令移至Begin和End块,内存使用恢复正常,但即便调用[System.GC]::Collect()也无法释放内存。以下解析IISCommitDelay的具体作用及内存异常的成因:

一、IISCommitDelay的核心作用

  • Start-IISCommitDelay和Stop-IISCommitDelay是IIS管理模块的配套命令,核心是批量延迟提交配置变更,避免频繁写入磁盘和刷新配置。
  • 默认情况下,每一次IIS配置修改(比如新建站点、调整应用池)都会立即写入applicationHost.config,并触发IIS的配置刷新操作。
  • 调用Start-IISCommitDelay后,后续所有配置变更都会暂存到内存缓存中,直到执行Stop-IISCommitDelay时,才会一次性把所有缓存的变更写入磁盘,完成一次配置刷新。
  • 这个机制专为批量配置场景设计:减少磁盘IO次数,避免多次刷新导致的IIS服务波动,大幅提升大规模配置操作的效率。

二、内存异常的成因分析

1. Process块反复调用导致内存激增

  • 每次在Process块中调用Start-IISCommitDelay,都会创建一个独立的配置延迟会话。虽然Stop-IISCommitDelay会结束会话,但PowerShell与IIS配置API的交互中,可能存在未被正确释放的资源句柄或内存缓存。
  • 100次循环就会生成100个独立会话,每个会话都会暂存当前配置副本、变更记录等数据,这些临时资源无法及时回收,导致内存占用不断累加。
  • 另外,New-IISSite等命令返回的对象(如$TestSite)在延迟会话中会被额外引用,无法被.NET GC及时回收,进一步加剧内存占用。

2. Begin/End块调用后内存无法释放的原因

  • 把Start-IISCommitDelay放在Begin块、Stop-IISCommitDelay放在End块时,只会创建一个全局延迟会话,所有配置变更都在同一个会话中处理,因此不会出现内存累加激增的情况。
  • 调用[System.GC]::Collect()无效,是因为IIS配置API内部持有部分非托管资源(比如文件句柄、COM对象引用),这些资源不受.NET GC直接管理。即便触发GC,非托管资源也需要特定清理机制释放,而PowerShell的IIS模块并未完全暴露这些清理接口。
  • 此外,PowerShell的模块加载上下文本身会保留部分配置缓存,这类缓存不会因GC调用立即释放,需等到PowerShell会话结束后才会被清理。

测试用PowerShell代码

#Requires -RunAsAdministrator
#Requires -PSEdition Desktop
#Requires -Version 5.1
#Requires -Modules webadministration, iisadministration

Import-Module iisadministration 
Import-Module webadministration 
$NumberOfnewWebsites = 1..100
$BindingPort = 80   # Bindingport is the place to start - script will test ports until it finds an unused port and use that port for the website.    
$NumberOfnewWebsites | ForEach-Object -Begin { 
    $WebsiteNumber = 0
    $PortsInUse = (Get-NetTCPConnection | Where-Object localport -GE $BindingPort).LocalPort | Sort-Object
    $Bindingports = $NumberOfnewWebsites
    for ($i = 0; $i -lt $NumberOfnewWebsites.Count; $i++) {
        $BindingPortInUse = $true
        while ($BindingPortInUse) {
            if ($BindingPort -in $PortsInUse) { $Bindingport = $BindingPort + 1
            } else {
                $BindingPortInUse = $false
                $Bindingports[$i] = $BindingPort
                $Bindingport = $BindingPort + 1
            }
        }
    }
    # Start-IISCommitDelay
    
} -Process {
    Start-IISCommitDelay
    
    $WebsiteNameTemplate = "TestWebsite" # Actual WebsiteName will be websitename + WebsiteID
    $WebsiteID = 1  # If 1 is not free, search for the next free ID
    $ApplicationNameTemplate = "TestApplication"
    
    # Search for free websiteID. Startingpoint is $WebsiteID
    $IDsInUse = (Get-IISSite).ID
    while ($WebsiteID -in $IDsInUse) {
        $WebsiteID = $WebsiteID + 1
    }
    
    # Build variables for later
    $WebsiteName = $WebsiteNameTemplate + "_" + $WebsiteID
    $WebsiteHostHeader = [System.Net.Dns]::GetHostEntry($env:COMPUTERNAME).HostName
    $Binding = "*:" + $Bindingports[$WebsiteNumber] + ":" + $WebsiteHostHeader
    $ApplicationName = $ApplicationNameTemplate + "_" + $WebsiteID
    
    # Write some progress (and no, it does not affect performance)
    $WebsiteNumber += 1 
    $Status = $WebsiteNumber.ToString() + " of " + ($NumberOfnewWebsites).count.ToString()
    Write-Progress -Activity "Creating Websites" -CurrentOperation $WebsiteName -Status $Status -PercentComplete (($WebsiteNumber / $NumberOfnewWebsites.count) * 100)

    # Create folders for the application
    $WebsiteDirectory = "d:\websites\jndata\$WebsiteName"
    $ApplicationDirectory = "d:\websites\jndata\Applications\$ApplicationName"
    New-Item -ItemType Directory -Path $WebsiteDirectory -Force | Out-Null
    New-Item -ItemType Directory -Path $ApplicationDirectory -Force | Out-Null

    # Create Website with a webapplication
    # Website
    $ConfigSectionCollection = Get-IISConfigSection -SectionPath "system.applicationHost/applicationPools" | Get-IISConfigCollection
    New-IISConfigCollectionElement -ConfigCollection $configSectionCollection -ConfigAttribute @{"name" = "$ApplicationName"; "autoStart" = $true; "managedPipelineMode" = "Integrated" }
    $TestSite = New-IISSite -Name $WebsiteName -BindingInformation $Binding -PhysicalPath $WebsiteDirectory -Passthru
    # Application
    $TestSite.Applications.Add("/$ApplicationName", $ApplicationDirectory) | Out-Null
    $TestSite.Applications["/$ApplicationName"].ApplicationPoolName = "$ApplicationName" 
    # Commit (To storage?)
    Stop-IISCommitDelay
} -End { 
    # Stop-IISCommitDelay
}
$Status = "Done. Created " + ($NumberOfnewWebsites).count.ToString() + " websites."
Write-Progress -Status $Status -Activity "Creating Websites"

内容的提问来源于stack exchange,提问作者Morten Worm Due

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 17:02:05