jvm和dotnet程序cpu/內(nèi)存/線程問題排查方案

jvm

環(huán)境準(zhǔn)備

  1. 確保目標(biāo)機(jī)器上已安裝:

    • PowerShell(Windows PowerShell 5.1 或 PowerShell Core 均可)
    • JDK(含 jstat, jmap, jcmd, jstack 等工具; 建議 JDK 11+)
  2. 將以下腳本文件 java_pid_analysis.ps1
    (本腳本適用于java22,其他版本要按需調(diào)整命令)放到某固定目錄(如
    D:\Scripts\):

      param(
        [Parameter(Mandatory=$true)]
        [int]$targetPid,
        [Parameter(Mandatory=$true)]
        [string]$jdkPath
    )
    
    # Set JDK tools path
    $jcmdPath = Join-Path -Path $jdkPath -ChildPath "bin\jcmd.exe"
    $jstatPath = Join-Path -Path $jdkPath -ChildPath "bin\jstat.exe"
    $jmapPath = Join-Path -Path $jdkPath -ChildPath "bin\jmap.exe"
    $jstackPath = Join-Path -Path $jdkPath -ChildPath "bin\jstack.exe"
    
    # Create analysis report file
    $reportFile = "jvm_analysis_$targetPid.txt"
    $currentTime = Get-Date -Format "yyyy-MM-dd HH:mm"
    
    Write-Host "==> Inspecting JVM Process PID=$targetPid" -ForegroundColor Cyan
    Write-Host "==> Using JDK at: $jdkPath" -ForegroundColor Cyan
    
    # Initialize report content
    $reportContent = @"
    JVM Analysis Report
    ===================
    
    - 時(shí)間: $currentTime
    - 目標(biāo) PID: $targetPid
    
    "@
    
    # 1. OS 層面 CPU/內(nèi)存/線程
    Write-Host "`n==> OS Metrics (Get-Process)" -ForegroundColor Cyan
    $processInfo = Get-Process -Id $targetPid -ErrorAction SilentlyContinue
    
    if (-not $processInfo) {
        Write-Host "Process with PID $targetPid not found!" -ForegroundColor Red
        exit 1
    }
    
    $processInfo = $processInfo | Select-Object `
        @{Name='CPU(s)';Expression={$_.CPU}}, `
        @{Name='WorkingSet(MB)';Expression={[math]::Round($_.WS/1MB,2)}}, `
        @{Name='Threads';Expression={$_.Threads.Count}}
    
    $processInfo | Format-Table -AutoSize
    
    # Add to report
    $osMetrics = "- OS CPU(s): {0}s; WorkingSet: {1}MB; 線程數(shù): {2}" -f
        $processInfo.'CPU(s)',
        $processInfo.'WorkingSet(MB)',
        $processInfo.'Threads'
    $reportContent += $osMetrics + "`n"
    
    # 2. jstat: GC 與堆使用(每 1s 一次, 共 1 次)
    Write-Host "`n==> jstat -gcutil" -ForegroundColor Cyan
    try {
        $jstatOutput = & $jstatPath -gcutil $targetPid 1000 1 2>&1
        $jstatOutput | Out-Host
    
        # Parse jstat output (assuming the format is standard)
        if ($jstatOutput.Count -gt 1) {
            $jstatValues = $jstatOutput[1] -split '\s+'
            $jstatLine = "- jstat: E={0}% O={1}% M={2}% CCS={3}% YGC={4} YGCT={5} FGC={6} FGCT={7} GCT={8}" -f
                $jstatValues[6], $jstatValues[4], $jstatValues[7], $jstatValues[8],
                $jstatValues[9], $jstatValues[10], $jstatValues[11], $jstatValues[12], $jstatValues[13]
            $reportContent += $jstatLine + "`n"
        }
    } catch {
        $reportContent += "- jstat: Failed to execute`n"
        Write-Host "jstat failed: $_" -ForegroundColor Red
    }
    
    # 3. jmap: 堆概要信息
    Write-Host "`n==> jmap -heap" -ForegroundColor Cyan
    try {
        $jmapOutput = & $jcmdPath $targetPid GC.heap_info 2>&1
        $jmapOutput | Out-Host
        $reportContent += "- jcmd GC.heap_info: `n" + ($jmapOutput -join "`n") + "`n"
    } catch {
        $reportContent += "- jmap: Failed to execute`n"
        Write-Host "jmap failed: $_" -ForegroundColor Red
    }
    
    # 4. jcmd: Native Memory Summary(JDK14+)
    Write-Host "`n==> jcmd VM.native_memory summary" -ForegroundColor Cyan
    try {
        $nativeMemOutput = & $jcmdPath $targetPid VM.flags 2>&1
        $nativeMemOutput | Out-Host
        if ($nativeMemOutput -match "NativeMemoryTracking") {
            $nmtStatus = $matches[0]
            $reportContent += "- NativeMemoryTracking 狀態(tài): $nmtStatus`n"
        } else {
            $reportContent += "- NativeMemoryTracking 未啟用`n"
        }
    } catch {
        $reportContent += "- native_memory: Failed to execute`n"
        Write-Host "jcmd failed: $_" -ForegroundColor Red
    }
    
    # 5. JVM 線程數(shù)
    Write-Host "`n==> jcmd Thread.print (count)" -ForegroundColor Cyan
    try {
        $threadCountOutput = & $jcmdPath $targetPid Thread.print 2>&1
        $threadCount = $threadCountOutput | Select-String "Total threads:" -SimpleMatch
        $threadCount | Out-Host
        if ($threadCount) {
            $reportContent += "- 線程總數(shù): " + ($threadCount -replace ".*Total threads:\s*", "") + "`n"
        } else {
            $reportContent += "- 線程總數(shù): Could not determine`n"
        }
    } catch {
        $reportContent += "- 線程總數(shù): Failed to execute`n"
        Write-Host "Thread count failed: $_" -ForegroundColor Red
    }
    
    # 6. 線程 Dump(含鎖信息)
    $dumpFile = "thread_dump_$targetPid.txt"
    Write-Host "`n==> Generating thread dump to $dumpFile" -ForegroundColor Cyan
    try {
        & $jstackPath -l $targetPid 2>&1 > $dumpFile
        Write-Host "    Thread dump saved." -ForegroundColor Green
        $reportContent += "- 線程 dump見: $dumpFile`n"
    } catch {
        $reportContent += "- 線程 dump: Failed to generate`n"
        Write-Host "Thread dump failed: $_" -ForegroundColor Red
    }
    
    # Save the report
    $reportContent | Out-File -FilePath $reportFile -Encoding UTF8
    Write-Host "`n==> Analysis report saved to $reportFile" -ForegroundColor Green
    

解鎖并允許腳本執(zhí)行

現(xiàn)場機(jī)器常會因 PowerShell 執(zhí)行策略阻止腳本運(yùn)行,
推薦使用「方法二」一次性繞過:

腳本需要兩個(gè)參數(shù):

  • 第一個(gè)參數(shù),pid
  • 第二個(gè)參數(shù), jdk路徑,注意到JAVAHOME路徑.
powershell.exe -ExecutionPolicy Bypass -File "D:\Scripts\java_pid_analysis.ps1" 12345 cd-to-java-path
  • 僅對本次執(zhí)行生效, 不改動機(jī)器配置.
  • 若要長期使用, 可參考下面" 附錄" 修改執(zhí)行策略.

執(zhí)行腳本在控制臺的輸出可能如下:

PS D:\scada\project\預(yù)灌裝NEW>  .\check-jvm-by-pid.ps1 21932 D:\scada\chutian-front\front\scada\dist\win-unpacked\resources\app\build\electron\env\jdk-22.0.2 > console_log.txt
==> Inspecting JVM Process PID=21932
==> Using JDK at: D:\scada\chutian-front\front\scada\dist\win-unpacked\resources\app\build\electron\env\jdk-22.0.2

==> OS Metrics (Get-Process)

==> jstat -gcutil
  S0     S1     E      O      M     CCS    YGC     YGCT     FGC    FGCT     CGC    CGCT       GCT
     -  98.33   0.53  96.19  97.11  92.64    573    44.998    50     8.887   304     1.127    55.013

==> jmap -heap
21932:
 garbage-first heap   total reserved 4184064K, committed 4184064K, used 2601112K [0x0000000700a00000, 0x0000000800000000)
  region size 2048K, 30 young (61440K), 29 survivors (59392K)
 Metaspace       used 109818K, committed 113088K, reserved 1179648K
  class space    used 21345K, committed 23040K, reserved 1048576K

==> jcmd VM.native_memory summary
21932:
-XX:CICompilerCount=4 -XX:ConcGCThreads=2 -XX:G1ConcRefinementThreads=8 -XX:G1EagerReclaimRemSetThreshold=16 -XX:G1HeapRegionSize=2097152 -XX:G1RemSetArrayOfCardsEntries=16 -XX:G1RemSetHowlMaxNumBuckets=8 -XX:G1RemSetHowlNumBuckets=8 -XX:InitialHeapSize=268435456 -XX:MarkStackSize=4194304 -XX:MaxHeapSize=4284481536 -XX:MaxNewSize=2569011200 -XX:MinHeapDeltaBytes=2097152 -XX:MinHeapSize=8388608 -XX:NonNMethodCodeHeapSize=5839372 -XX:NonProfiledCodeHeapSize=122909434 -XX:ProfiledCodeHeapSize=122909434 -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:SoftMaxHeapSize=4284481536 -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseG1GC -XX:-UseLargePagesIndividualAllocation

==> jcmd Thread.print (count)

==> Generating thread dump to thread_dump_21932.txt
    Thread dump saved.

==> Analysis report saved to jvm_analysis_21932.txt

腳本產(chǎn)物

  • threaddump21932.txt jstack的結(jié)果

  • jvmanalysis21932.txt 匯總分析,內(nèi)容如下:

       - 時(shí)間: 2025-06-16 14:46
       - 目標(biāo) PID: 21932
       - OS CPU(s): 2228s; WorkingSet: 3868.61MB; 線程數(shù): 136
       - jstat: E=92.64% O=96.19% M=573% CCS=44.998% YGC=50 YGCT=8.887 FGC=304 FGCT=1.127 GCT=55.013
       - jcmd GC.heap_info:
       21932:
    garbage-first heap   total reserved 4184064K, committed 4184064K, used 2601112K [0x0000000700a00000, 0x0000000800000000)
     region size 2048K, 30 young (61440K), 29 survivors (59392K)
    Metaspace       used 109818K, committed 113088K, reserved 1179648K
     class space    used 21345K, committed 23040K, reserved 1048576K
       - NativeMemoryTracking Native分配內(nèi)存: Could not determine
       - 現(xiàn)成dump見: thread_dump_21932.txt
    

標(biāo)準(zhǔn)診斷流程

按照以下流程, 讓現(xiàn)場同事快速上手:

  1. 確認(rèn)目標(biāo) PID

    • 使用任務(wù)管理器或命令行:

      Get-Process -Name java | Format-Table Id, ProcessName, CPU, WS, StartTime
      
    • 選定要診斷的 JVM 進(jìn)程的 PID.

  2. 執(zhí)行診斷腳本

    • 在 PowerShell 中運(yùn)行:

      cd D:\Scripts\
      powershell.exe -ExecutionPolicy Bypass -File .\java_pid_analysis.ps1 -pid <目標(biāo)PID>
      
    • 例如:

      powershell.exe -ExecutionPolicy Bypass -File .\java_pid_analysis.ps1 -pid 9876
      
  3. 查看輸出結(jié)果

    • OS Metrics: CPU(s), WorkingSet(MB), 線程數(shù).
    • jstat -gcutil: 堆各代使用率及 GC 次數(shù)/耗時(shí).
    • jmap -heap: 詳細(xì)堆分代信息, GC 算法.
    • jcmd VM.nativememory summary: Native 內(nèi)存分配.
    • jcmd Thread.print (count): JVM 內(nèi)部線程總數(shù).
    • threaddump_.txt: 完整線程棧(含鎖信息), 保存在腳本目錄.
  4. 問題定位

    • CPU 過高: 定位到哪個(gè)線程占用最高, 可在 thread_dump
      中搜索 nid=0x... 對應(yīng)十進(jìn)制線程 ID, 然后結(jié)合操作系統(tǒng)工具(如
      Process Explorer)查找.
    • 內(nèi)存 泄漏: 關(guān)注 jmap -heap 中 Old Gen 使用率持續(xù)上升,
      jcmd native_memory 中 C 堆本地分配異常.
    • 線程阻塞/死鎖: 在 thread_dump 中搜索 BLOCKED,
      WAITING, Deadlock.
  5. 匯報(bào)模板 現(xiàn)場診斷完成后, 可用如下模板整理匯報(bào):

    - 時(shí)間: 2025-06-16 14:30
    - 目標(biāo) PID: 9876
    - OS CPU(s): 120.34s; WorkingSet: 1024MB; 線程數(shù): 250
    - jstat: E=45.67% O=23.45% M=78.90% ...
    - jmap -heap 摘要: OldGen=512MB/1024MB (50%), GC 算法: G1
    - native_memory summary: C 堆 300MB; 代碼緩存 50MB; 線程棧 200MB
    - 線程 dump: 見 thread_dump_9876.txt
    - 初步結(jié)論: OldGen 使用過高 + 多線程阻塞于 XX 鎖, 建議...
    

附錄: 修改執(zhí)行策略(可選)

如果希望不帶 -ExecutionPolicy 參數(shù)即可運(yùn)行, 可臨時(shí)提升用戶策略:

# 以管理員身份啟動 PowerShell 后執(zhí)行
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

僅本用戶生效, 本地腳本可直接執(zhí)行, 網(wǎng)絡(luò)下載腳本需簽名.

這樣, 一套從" 獲取 PID → 運(yùn)行腳本 → 查看結(jié)果 → 問題定位 → 匯報(bào)"
的流程就完成了, 現(xiàn)場同事照流程走即可快速精準(zhǔn)地收集 JVM 運(yùn)行態(tài)信息.

.Net

環(huán)境準(zhǔn)備

  1. 確保目標(biāo)機(jī)器上已安裝:

    • PowerShell (Windows PowerShell 5.1 或 PowerShell Core 均可)
    • .NET SDK (包含 dotnet CLI 工具)
    • 診斷工具 (dotnet-trace, dotnet-dump, dotnet-stack 等)
  2. 安裝必要的診斷工具:

    # 安裝 dotnet-trace 工具
    dotnet tool install --global dotnet-trace
    
    # 安裝 dotnet-dump 工具
    dotnet tool install --global dotnet-dump
    
    # 安裝 dotnet-stack 工具
    dotnet tool install --global dotnet-stack
    
  3. 將以下腳本文件 dotnet-performance-analysis.ps1 放到某固定目錄(如
    D:\Scripts\):

    param(
        [Parameter(Mandatory=$true)]
        [int]$targetPid,
        [string]$outputPath = "."
    )
    
    # 創(chuàng)建輸出目錄(如果不存在)
    if (-not (Test-Path -Path $outputPath)) {
        New-Item -ItemType Directory -Path $outputPath | Out-Null
    }
    
    # 創(chuàng)建分析報(bào)告文件
    $reportFile = Join-Path -Path $outputPath -ChildPath "dotnet_analysis_$targetPid.txt"
    $currentTime = Get-Date -Format "yyyy-MM-dd HH:mm"
    
    Write-Host "==> 檢查 .NET 進(jìn)程 PID=$targetPid" -ForegroundColor Cyan
    
    # 初始化報(bào)告內(nèi)容
    $reportContent = @"
    .NET 進(jìn)程分析報(bào)告
    ===================
    
    - 時(shí)間: $currentTime
    - 目標(biāo) PID: $targetPid
    
    "@
    
    # 1. OS 層面 CPU/內(nèi)存/線程
    Write-Host "`n==> OS 指標(biāo) (Get-Process)" -ForegroundColor Cyan
    $processInfo = Get-Process -Id $targetPid -ErrorAction SilentlyContinue
    
    if (-not $processInfo) {
        Write-Host "未找到 PID $targetPid 的進(jìn)程!" -ForegroundColor Red
        exit 1
    }
    
    $processInfo = $processInfo | Select-Object `
        @{Name='CPU(s)';Expression={$_.CPU}}, `
        @{Name='WorkingSet(MB)';Expression={[math]::Round($_.WS/1MB,2)}}, `
        @{Name='PrivateMemory(MB)';Expression={[math]::Round($_.PM/1MB,2)}}, `
        @{Name='VirtualMemory(MB)';Expression={[math]::Round($_.VM/1MB,2)}}, `
        @{Name='Threads';Expression={$_.Threads.Count}}, `
        @{Name='Handles';Expression={$_.HandleCount}}
    
    $processInfo | Format-Table -AutoSize
    
    # 添加到報(bào)告
    $osMetrics = "- OS CPU(s): {0}s; WorkingSet: {1}MB; 專用內(nèi)存: {2}MB; 虛擬內(nèi)存: {3}MB; 線程數(shù): {4}; 句柄數(shù): {5}" -f
        $processInfo.'CPU(s)',
        $processInfo.'WorkingSet(MB)',
        $processInfo.'PrivateMemory(MB)',
        $processInfo.'VirtualMemory(MB)',
        $processInfo.'Threads',
        $processInfo.'Handles'
    $reportContent += $osMetrics + "`n"
    
    # 2. 檢查是否安裝了 dotnet-trace 工具
    Write-Host "`n==> 檢查 dotnet-trace 工具" -ForegroundColor Cyan
    try {
        $dotnetTraceVersion = dotnet tool list --global | Select-String "dotnet-trace"
        if (-not $dotnetTraceVersion) {
            Write-Host "未安裝 dotnet-trace 工具,無法收集跟蹤信息" -ForegroundColor Yellow
            $reportContent += "- dotnet-trace: 未安裝,無法收集跟蹤信息`n"
        } else {
            Write-Host "已安裝 $dotnetTraceVersion" -ForegroundColor Green
            $reportContent += "- dotnet-trace: $dotnetTraceVersion`n"
    
            # 收集 CPU 采樣跟蹤(5秒)
            $traceFile = Join-Path -Path $outputPath -ChildPath "trace_$targetPid.nettrace"
            Write-Host "`n==> 收集 CPU 采樣跟蹤 (5秒)" -ForegroundColor Cyan
            try {
                & dotnet-trace collect --process-id $targetPid --profile cpu-sampling --duration 00:00:05 --output $traceFile
                $reportContent += "- CPU 采樣跟蹤: $traceFile`n"
    
                # 轉(zhuǎn)換為 SpeedScope 格式
                $speedscopeFile = Join-Path -Path $outputPath -ChildPath "trace_$targetPid.speedscope.json"
                Write-Host "`n==> 轉(zhuǎn)換為 SpeedScope 格式" -ForegroundColor Cyan
                & dotnet-trace convert $traceFile --format speedscope --output $speedscopeFile
                $reportContent += "- SpeedScope 文件: $speedscopeFile (可在 https://www.speedscope.app/ 查看)`n"
            } catch {
                $reportContent += "- CPU 采樣跟蹤: 收集失敗 - $_`n"
                Write-Host "跟蹤收集失敗: $_" -ForegroundColor Red
            }
        }
    } catch {
        $reportContent += "- dotnet-trace: 檢查失敗 - $_`n"
        Write-Host "dotnet-trace 檢查失敗: $_" -ForegroundColor Red
    }
    
    # 3. 檢查是否安裝了 dotnet-dump 工具
    Write-Host "`n==> 檢查 dotnet-dump 工具" -ForegroundColor Cyan
    try {
        $dotnetDumpVersion = dotnet tool list --global | Select-String "dotnet-dump"
        if (-not $dotnetDumpVersion) {
            Write-Host "未安裝 dotnet-dump 工具,無法收集內(nèi)存轉(zhuǎn)儲" -ForegroundColor Yellow
            $reportContent += "- dotnet-dump: 未安裝,無法收集內(nèi)存轉(zhuǎn)儲`n"
        } else {
            Write-Host "已安裝 $dotnetDumpVersion" -ForegroundColor Green
            $reportContent += "- dotnet-dump: $dotnetDumpVersion`n"
    
            # 收集內(nèi)存轉(zhuǎn)儲
            $dumpFile = Join-Path -Path $outputPath -ChildPath "dump_$targetPid.dmp"
            Write-Host "`n==> 收集內(nèi)存轉(zhuǎn)儲 (Mini 類型)" -ForegroundColor Cyan
            try {
                & dotnet-dump collect --process-id $targetPid --type Mini --output $dumpFile
                $reportContent += "- 內(nèi)存轉(zhuǎn)儲: $dumpFile (使用 'dotnet-dump analyze $dumpFile' 分析)`n"
            } catch {
                $reportContent += "- 內(nèi)存轉(zhuǎn)儲: 收集失敗 - $_`n"
                Write-Host "內(nèi)存轉(zhuǎn)儲收集失敗: $_" -ForegroundColor Red
            }
        }
    } catch {
        $reportContent += "- dotnet-dump: 檢查失敗 - $_`n"
        Write-Host "dotnet-dump 檢查失敗: $_" -ForegroundColor Red
    }
    
    # 4. 檢查是否安裝了 dotnet-stack 工具
    Write-Host "`n==> 檢查 dotnet-stack 工具" -ForegroundColor Cyan
    try {
        $dotnetStackVersion = dotnet tool list --global | Select-String "dotnet-stack"
        if (-not $dotnetStackVersion) {
            Write-Host "未安裝 dotnet-stack 工具,無法收集堆棧跟蹤" -ForegroundColor Yellow
            $reportContent += "- dotnet-stack: 未安裝,無法收集堆棧跟蹤`n"
        } else {
            Write-Host "已安裝 $dotnetStackVersion" -ForegroundColor Green
            $reportContent += "- dotnet-stack: $dotnetStackVersion`n"
    
            # 收集堆棧跟蹤
            $stackFile = Join-Path -Path $outputPath -ChildPath "stack_$targetPid.txt"
            Write-Host "`n==> 收集堆棧跟蹤" -ForegroundColor Cyan
            try {
                & dotnet-stack report --process-id $targetPid > $stackFile
                $reportContent += "- 堆棧跟蹤: $stackFile`n"
            } catch {
                $reportContent += "- 堆棧跟蹤: 收集失敗 - $_`n"
                Write-Host "堆棧跟蹤收集失敗: $_" -ForegroundColor Red
            }
        }
    } catch {
        $reportContent += "- dotnet-stack: 檢查失敗 - $_`n"
        Write-Host "dotnet-stack 檢查失敗: $_" -ForegroundColor Red
    }
    
    # 保存報(bào)告
    $reportContent | Out-File -FilePath $reportFile -Encoding UTF8
    Write-Host "`n==> 分析報(bào)告已保存到 $reportFile" -ForegroundColor Green
    
    # 提供分析建議
    Write-Host "`n==> 分析建議:" -ForegroundColor Cyan
    Write-Host "1. 查看 CPU 采樣跟蹤: 在 https://www.speedscope.app/ 上傳 $speedscopeFile 文件" -ForegroundColor White
    Write-Host "2. 分析內(nèi)存轉(zhuǎn)儲: 使用命令 'dotnet-dump analyze $dumpFile'" -ForegroundColor White
    Write-Host "3. 查看堆棧跟蹤: 打開 $stackFile 文件" -ForegroundColor White
    

解鎖并允許腳本執(zhí)行

現(xiàn)場機(jī)器常會因 PowerShell 執(zhí)行策略阻止腳本運(yùn)行,
推薦使用「方法二」一次性繞過:

powershell.exe -ExecutionPolicy Bypass -File "D:\Scripts\dotnet-performance-analysis.ps1" -targetPid 12345
  • 僅對本次執(zhí)行生效, 不改動機(jī)器配置.
  • 若要長期使用, 可參考下面"附錄"修改執(zhí)行策略.

診斷工具詳解

dotnet-trace: 性能跟蹤與火焰圖

dotnet-trace 是一個(gè)跨平臺的 .NET 性能分析工具,可以收集運(yùn)行中的 .NET
應(yīng)用程序的性能數(shù)據(jù)。

  1. 安裝

    dotnet tool install -g dotnet-trace
    
  2. 基本用法

    1. 查看可用進(jìn)程:

      dotnet-trace ps
      
    2. 收集性能數(shù)據(jù):

      # 使用 CPU 采樣配置文件收集 5 秒的跟蹤
      dotnet-trace collect -p <PID> --profile cpu-sampling --duration 00:00:05
      
    3. 轉(zhuǎn)換為火焰圖格式:

      # 將結(jié)果轉(zhuǎn)換為火焰圖格式
      dotnet-trace convert <trace-file>.nettrace --format speedscope
      
    4. 查看火焰圖:

  3. 常用配置文件

    配置文件名稱 說明
    `cpu-sampling` 收集 CPU 采樣數(shù)據(jù),適合分析 CPU 使用率高的問題
    `gc-collect` 收集垃圾回收相關(guān)事件
    `gc-verbose` 收集詳細(xì)的垃圾回收信息
    `http` 收集 HTTP 請求相關(guān)事件
    `metrics-browser` 收集瀏覽器指標(biāo)

dotnet-dump: 內(nèi)存轉(zhuǎn)儲分析

dotnet-dump 是一個(gè)用于收集和分析 .NET
應(yīng)用程序內(nèi)存轉(zhuǎn)儲的工具,可以幫助診斷內(nèi)存泄漏、死鎖等問題。

  1. 安裝

    dotnet tool install -g dotnet-dump
    
  2. 基本用法

    1. 收集內(nèi)存轉(zhuǎn)儲:

      dotnet-dump collect -p <PID>
      
    2. 分析內(nèi)存轉(zhuǎn)儲:

      dotnet-dump analyze <dump-file>.dmp
      
  3. 常用分析命令

    在 dotnet-dump analyze 交互式會話中,可以使用以下命令:

    命令 說明
    `dumpheap` 顯示堆上的對象信息
    `gcroot` 查找對象的引用路徑
    `threads` 顯示所有線程信息
    `clrstack` 顯示托管代碼的堆棧跟蹤
    `dso` 顯示當(dāng)前堆棧邊界內(nèi)的所有托管對象
    `dumpobj` 顯示對象的詳細(xì)信息
    `dumparray` 顯示數(shù)組的詳細(xì)信息
    `dumpasync` 顯示異步狀態(tài)機(jī)信息
    `exit` 退出分析會話

dotnet-stack: 堆棧跟蹤

dotnet-stack 是一個(gè)用于收集 .NET
應(yīng)用程序線程堆棧跟蹤的工具,可以幫助診斷線程阻塞、死鎖等問題。

  1. 安裝

    dotnet tool install -g dotnet-stack
    
  2. 基本用法

    1. 查看可用進(jìn)程:

      dotnet-stack ps
      
    2. 收集堆棧跟蹤:

      dotnet-stack report -p <PID>
      
    3. 收集到文件:

      dotnet-stack report -p <PID> > stack.txt
      

標(biāo)準(zhǔn)診斷流程

按照以下流程,讓現(xiàn)場同事快速上手:

  1. 確認(rèn)目標(biāo) PID

    • 使用任務(wù)管理器或命令行:

      Get-Process -Name *dotnet* | Format-Table Id, ProcessName, CPU, WS, StartTime
      
    • 選定要診斷的 .NET 進(jìn)程的 PID。

  2. 執(zhí)行診斷腳本

    • 在 PowerShell 中運(yùn)行:

      cd D:\Scripts\
      powershell.exe -ExecutionPolicy Bypass -File .\dotnet-performance-analysis.ps1 -targetPid <目標(biāo)PID>
      
    • 例如:

      powershell.exe -ExecutionPolicy Bypass -File .\dotnet-performance-analysis.ps1 -targetPid 9876
      
  3. 查看輸出結(jié)果

    • OS Metrics: CPU(s), WorkingSet(MB), 專用內(nèi)存(MB),
      虛擬內(nèi)存(MB), 線程數(shù), 句柄數(shù)。
    • dotnet-trace: CPU 采樣跟蹤文件和 SpeedScope 文件。
    • dotnet-dump: 內(nèi)存轉(zhuǎn)儲文件。
    • dotnet-stack: 堆棧跟蹤文件。
  4. 問題定位

    • CPU 過高:使用 SpeedScope 查看火焰圖,定位熱點(diǎn)方法。
    • 內(nèi)存 泄漏:使用 dotnet-dump analyze
      分析內(nèi)存轉(zhuǎn)儲,查找大對象和引用路徑。
    • 若 *線程阻塞/死鎖*:在堆棧跟蹤中查找 BLOCKED 或 WAITING
      狀態(tài)的線程。
  5. 匯報(bào)模板 現(xiàn)場診斷完成后,可用如下模板整理匯報(bào):

    - 時(shí)間: 2025-06-16 14:30
    - 目標(biāo) PID: 9876
    - OS CPU(s): 120.34s; WorkingSet: 1024MB; 專用內(nèi)存: 800MB; 虛擬內(nèi)存: 2048MB; 線程數(shù): 45; 句柄數(shù): 1200
    - CPU 熱點(diǎn): 方法 A (30%), 方法 B (15%), 方法 C (10%)
    - 內(nèi)存分析: 發(fā)現(xiàn)類型 X 的實(shí)例過多 (10000+),可能存在內(nèi)存泄漏
    - 線程狀態(tài): 3 個(gè)線程阻塞在 Y 鎖上,可能存在死鎖
    - 初步結(jié)論: 內(nèi)存使用過高 + 線程阻塞,建議優(yōu)化 X 類型的對象生命周期管理
    

常見問題分析與解決方案

CPU 使用率過高

  1. 癥狀

    • 應(yīng)用響應(yīng)緩慢
    • 任務(wù)管理器顯示 CPU 使用率高
    • 服務(wù)器負(fù)載高
  2. 分析方法

    1. 使用 dotnet-trace 收集 CPU 采樣數(shù)據(jù):

      dotnet-trace collect -p <PID> --profile cpu-sampling
      
    2. 轉(zhuǎn)換為 SpeedScope 格式并在 https://www.speedscope.app/ 查看

    3. 在火焰圖中尋找占用 CPU 時(shí)間最多的方法(最寬的部分)

  3. 常見原因與解決方案

    • *無限循環(huán)或低效算法*:優(yōu)化算法,避免不必要的循環(huán)
    • *過度分配/回收對象*:使用對象池,減少臨時(shí)對象創(chuàng)建
    • *不必要的字符串操作*:使用 StringBuilder,避免頻繁字符串拼接
    • *線程爭用*:減少鎖的粒度,使用更高效的并發(fā)模式

內(nèi)存泄漏

  1. 癥狀

    • 應(yīng)用內(nèi)存使用隨時(shí)間持續(xù)增長
    • 頻繁的垃圾回收
    • 最終導(dǎo)致 OutOfMemoryException
  2. 分析方法

    1. 使用 dotnet-dump 收集內(nèi)存轉(zhuǎn)儲:

      dotnet-dump collect -p <PID>
      
    2. 分析內(nèi)存轉(zhuǎn)儲:

      dotnet-dump analyze <dump-file>.dmp
      
    3. 使用以下命令查找大對象:

      dumpheap -stat    # 按類型統(tǒng)計(jì)對象
      dumpheap -type <TypeName>  # 查看特定類型的所有對象
      gcroot <address>  # 查找對象的引用路徑
      
  3. 常見原因與解決方案

    • *事件訂閱未取消*:確保取消事件訂閱,使用弱引用事件模式
    • *靜態(tài)集合持有對象*:避免在靜態(tài)集合中存儲長生命周期對象
    • *未釋放的資源*:正確實(shí)現(xiàn) IDisposable 模式,使用 using 語句
    • *緩存未設(shè)置上限*:使用 MemoryCache 并設(shè)置適當(dāng)?shù)倪^期策略

線程死鎖

  1. 癥狀

    • 應(yīng)用掛起或無響應(yīng)
    • 某些操作永遠(yuǎn)不會完成
    • 線程數(shù)持續(xù)增長
  2. 分析方法

    1. 使用 dotnet-stack 收集堆棧跟蹤:

      dotnet-stack report -p <PID> > stack.txt
      
    2. 在堆棧跟蹤中查找 BLOCKED 或 WAITING 狀態(tài)的線程

    3. 分析線程之間的依賴關(guān)系,尋找循環(huán)依賴

  3. 常見原因與解決方案

    • *嵌套鎖*:避免在持有一個(gè)鎖的同時(shí)獲取另一個(gè)鎖
    • *不一致的鎖順序*:確保始終以相同的順序獲取多個(gè)鎖
    • *同步上下文死鎖*:使用 ConfigureAwait(false)
      避免上下文切換死鎖
    • *資源爭用*:使用更細(xì)粒度的鎖,或考慮無鎖數(shù)據(jù)結(jié)構(gòu)

實(shí)際案例分析

案例一:線程數(shù)不斷增長

  1. 問題描述

    應(yīng)用程序運(yùn)行一段時(shí)間后,線程數(shù)不斷增長,最終導(dǎo)致系統(tǒng)資源耗盡。

  2. 分析過程

    1. 使用 dotnet-stack 收集堆棧跟蹤
    2. 發(fā)現(xiàn)大量線程處于等待狀態(tài),且都在相似的代碼位置
    3. 檢查日志發(fā)現(xiàn)在數(shù)據(jù)采集部分和觸發(fā)數(shù)據(jù)積累的地方都打印了大量日志
  3. 解決方案

    1. 刪除采集部分的冗余日志
    2. 優(yōu)化日志級別,減少不必要的日志輸出
    3. 修改后線程數(shù)不再隨時(shí)間增長,系統(tǒng)能正常歸檔數(shù)據(jù)

案例二:CPU 使用率突然飆升

  1. 問題描述

    生產(chǎn)環(huán)境中的應(yīng)用在特定操作后 CPU 使用率突然飆升到 100%。

  2. 分析過程

    1. 使用 dotnet-trace 收集 CPU 采樣數(shù)據(jù)并生成火焰圖
    2. 在火焰圖中發(fā)現(xiàn)某個(gè)數(shù)據(jù)處理方法占用了大量 CPU 時(shí)間
    3. 代碼審查發(fā)現(xiàn)該方法中存在 O(n2) 復(fù)雜度的算法
  3. 解決方案

    1. 重構(gòu)算法,將復(fù)雜度降低到 O(n log n)
    2. 添加數(shù)據(jù)分頁處理,避免一次處理過多數(shù)據(jù)
    3. 優(yōu)化后 CPU 使用率降低到正常水平

附錄: 修改執(zhí)行策略(可選)

如果希望不帶 -ExecutionPolicy 參數(shù)即可運(yùn)行,可臨時(shí)提升用戶策略:

# 以管理員身份啟動 PowerShell 后執(zhí)行
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

僅本用戶生效,本地腳本可直接執(zhí)行,網(wǎng)絡(luò)下載腳本需簽名。

附錄: 診斷工具對比

工具 用途概覽 是否實(shí)時(shí) 輸出類型 常見用途
————– —————— —– ————– ——————-
`dotnet-trace` 實(shí)時(shí)采樣追蹤(類似 `perf`) 實(shí)時(shí) `.nettrace` 文件 性能熱點(diǎn)分析、高 CPU、函數(shù)采樣分析
`dotnet-dump` 獲取并分析進(jìn)程的內(nèi)存轉(zhuǎn)儲(Dump) 離線 `.dmp` 文件 內(nèi)存泄漏、線程死鎖、對象分析
`dotnet-stack` 獲取進(jìn)程的線程堆棧信息 實(shí)時(shí) 文本輸出 線程狀態(tài)分析、死鎖檢測

附錄: 實(shí)際使用場景

問題 推薦工具 說明
————- —————- —————————–
應(yīng)用 CPU 飆高 `dotnet-trace` 捕獲方法采樣,查看熱點(diǎn)代碼
某個(gè)請求特別慢 `dotnet-trace` 配合 EventPipe 分析慢操作
應(yīng)用突然掛住/假死 `dotnet-dump` 導(dǎo)出 dump 分析線程死鎖
內(nèi)存持續(xù)增長,懷疑內(nèi)存泄漏 `dotnet-dump` 使用 `dumpheap`, `gcroot` 定位引用鏈
異常頻繁但日志信息不夠 `dotnet-trace` 追蹤異常事件
離線/無法實(shí)時(shí)調(diào)試 `dotnet-dump` 可用于生產(chǎn)環(huán)境導(dǎo)出后離線分析

這樣,一套從"獲取 PID → 運(yùn)行腳本 → 查看結(jié)果 → 問題定位 →
匯報(bào)"的流程就完成了,現(xiàn)場同事照流程走即可快速精準(zhǔn)地收集 .NET
運(yùn)行態(tài)信息。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容