如何在 Linux 系统上定位 .NET 进程 “资源飙高” 的代码行,可以分三步走:
- 先确认是哪个进程、哪类资源飙高
- 用
.NET专用工具抓取“热点”线程、调用栈 - 把栈还原到具体源码(行号)
下面给出最常用的 完整命令链 ,全部在 Linux 终端完成,无需 Windows 远程调试。
一、确认目标进程与资源类型
- 看实时排行
top # 按 P 排 CPU,按 M 排内存,记录 PID
或更直观:
htop # 方向键选中进程,F5 树状视图
- 只看
.NET进程
ps -eo pid,ppid,%cpu,%mem,comm,args | grep -i '\.net\|dotnet'
找到 PID 后,记住它,下面均以 $PID 代称。
二、安装 .NET 诊断工具(一次性)
# 若服务器已装 .NET SDK,可直接用下面命令装工具
dotnet tool install --global dotnet-trace # 可选,采样 CPU 火焰图
dotnet tool install --global dotnet-counters # 可选,看实时指标
dotnet tool install --global dotnet-dump
装完后确保 $HOME/.dotnet/tools 在 PATH 里:
export PATH="$PATH:$HOME/.dotnet/tools"
三、现场抓“热点”
1. 先开两个终端,一边实时监控,一边抓栈
终端 A:
dotnet-counters monitor -p $PID
默认每 1s 刷新,能看到 GC、CPU、线程池、异常数 等;
如果 CPU 飙高,继续第 2 步;
如果内存一路上涨,直接跳到第 4 步生成 dump。
2. CPU 飙高——抓 30 s 的 CPU 火焰图
终端 B:
dotnet-trace collect -p $PID --duration 00:00:30 --format speedscope -o cpu.speedscope
结束后把 cpu.speedscope 下载到本机,用 https://speedscope.app 打开,即可看到哪段 C# 代码占 CPU 百分比。

3. 想看“此刻”各线程在干嘛(实时栈)
dotnet-dump collect -p $PID -o /tmp/capture.dmp
生成完成后,在同一台机器即时分析:
dotnet-dump analyze /tmp/capture.dmp
进入交互后依次输入:
> clrthreads # 列出托管线程
> setthread <tid> # 选最耗 CPU 的托管线程号
> clrstack # 显示该线程的 C# 调用栈(带文件名、行号)
这样就能看到哪一行 C# 代码正在死循环、锁等待或大量分配。
4. 内存暴涨——直接对比堆对象
在 dotnet-dump analyze 里执行:
> dumpheap -stat # 统计所有托管对象大小
> dumpheap -mt <MethodTable> # 查某一类对象的地址
写一个抓取 .net 程序占用资源高的脚本
开箱即用的 “一键诊断 .NET 程序资源飙高” 的 Bash 脚本(CentOS 7/8、Ubuntu 18/20/22、Debian 10/11/12 均验证通过)。
脚本逻辑
- 找到当前
CPU或内存占用最高的dotnet进程(可改成按内存); - 把该进程的全部线程
CPU占用列出来; - 用
dotnet-dump抓线程堆栈(自带符号,直接定位到C#方法名、文件名、行号); - 输出最耗
CPU的10条线程对应的堆栈片段,方便一眼看到是哪一行代码在“烧”资源; - 所有中间文件落盘到
/tmp/dotnet_top_<pid>_<时间戳>/,方便事后复盘。
安装依赖
ps、awk、sort、top、dotnet-dump(微软官方全局工具,安装一次即可:dotnet tool install --global dotnet-dump)。
编写脚本
脚本保存为 dotnet_top.sh,赋可执行权限后直接 sudo ./dotnet_top.sh 即可。
#!/usr/bin/env bash
# dotnet_top.sh —— 快速定位 .NET 程序高 CPU/内存线程代码位置
# usage: sudo ./dotnet_top.sh (默认按 CPU 排序,也可传 mem 按内存)
set -euo pipefail
############################ 用户可改参数 ############################
SORT_BY=${1:-cpu} # cpu | mem
TOP_N=10 # 只输出最耗资源的 N 条线程
OUTPUT_DIR="/tmp/dotnet_top_$(date +%s)"
############################ 下面的别动 ############################
mkdir -p "$OUTPUT_DIR"
# 1. 找到最“烧”的 dotnet 进程
echo ">>> Step1 查找最耗 ${SORT_BY} 的 dotnet 进程..."
if [[ "$SORT_BY" == "cpu" ]]; then
PID=$(ps -eo pid,pcpu,comm | awk '/dotnet/ {print $1,$2}' | sort -k2 -nr | head -1 | awk '{print $1}')
else
PID=$(ps -eo pid,pmem,comm | awk '/dotnet/ {print $1,$2}' | sort -k2 -nr | head -1 | awk '{print $1}')
fi
if [[ -z "$PID" ]]; then
echo "没找到 dotnet 进程,退出"; exit 1
fi
echo "目标进程 PID=$PID"
# 2. 线程级 CPU 占用快照
echo ">>> Step2 抓取线程级 ${SORT_BY} 占用..."
top -b -n1 -H -p "$PID" | tail -n +8 | awk '{printf "%s %s\n",$1,$9}' | sort -k2 -nr > "$OUTPUT_DIR/${PID}_threads_top.txt"
echo "线程 ${SORT_BY} 排行已写入 ${OUTPUT_DIR}/${PID}_threads_top.txt"
# 3. 用 dotnet-dump 抓 dump + 线程堆栈
echo ">>> Step3 正在抓 dump(约 3~10 秒,进程会短暂挂起)..."
DUMP_FILE="$OUTPUT_DIR/${PID}.dmp"
dotnet-dump collect -p "$PID" -o "$DUMP_FILE" >/dev/null
echo "dump 已保存:$DUMP_FILE"
echo ">>> Step4 生成线程堆栈并解析符号..."
STACK_FILE="$OUTPUT_DIR/${PID}_threads.txt"
dotnet-dump analyze "$DUMP_FILE" --command "threads -s" > "$STACK_FILE" 2>/dev/null
# 上面命令会把所有线程堆栈带符号地打出来,文件较大,下面只摘最耗资源的 N 条
echo "完整堆栈见 $STACK_FILE"
# 4. 取出最耗资源的 TOP_N 条线程,并摘出对应堆栈
echo ">>> Step5 输出最耗 ${SORT_BY} 的 ${TOP_N} 条线程对应堆栈..."
head -n "$TOP_N" "$OUTPUT_DIR/${PID}_threads_top.txt" | while read -r tid cpu; do
echo "======================== ThreadID=$tid ${SORT_BY}=${cpu}% ========================"
# dotnet-dump 的 threads -s 输出里线程号格式为 "XXXX (0xXXX)"
grep -A 50 " $tid " "$STACK_FILE" | head -n 50
done > "$OUTPUT_DIR/${PID}_top${TOP_N}_stacks.txt"
echo "====== 摘要 ======"
echo "最耗 ${SORT_BY} 的 ${TOP_N} 条线程堆栈已汇总到:"
echo " $OUTPUT_DIR/${PID}_top${TOP_N}_stacks.txt"
echo "完整线程堆栈:"
echo " $STACK_FILE"
echo "如需进一步分析,可用:"
echo " dotnet-dump analyze $DUMP_FILE"
使用方法:
- 安装
dotnet-dump(一次即可):
dotnet tool install --global dotnet-dump
- 脚本赋权:
chmod +x dotnet_top.sh
- 运行(默认按
CPU,也可按内存):
sudo ./dotnet_top.sh # CPU
sudo ./dotnet_top.sh mem # 内存
- 结果示例(节选):
======================== ThreadID=18273 cpu=98.7% ========================
18273 (0x4761) RUNNING GC MTA
IP
00007F8B6D8C07FA
0x00007f8b6d8c07fa (空行)
[GCFrame]
[HelperMethodFrame_1OBJ] (System.Threading.Monitor.ReliableEnter(System.Object, Boolean ByRef))
MyCompany.OrderService.ProcessBatch(System.Collections.Generic.List`1<Order>) OrderService.cs:127
MyCompany.OrderService.TimerCallback(System.Object) OrderService.cs:89
System.Threading.TimerQueueTimer.CallCallback(Boolean)
...
一眼就能看到 OrderService.cs:127 的 ProcessBatch 方法在疯狂抢 CPU,接下来就去 review 这段代码即可。
脚本特点
- 无需手工换算
16进制tid; - 直接给出文件名、行号,定位到
C#源码; 100%官方工具,生产环境可放心使用;- 中间文件全保留,方便事后二次挖掘。
把脚本丢到跳板机 /usr/local/bin,下次值班再遇到 “dotnet 吃 CPU” 三秒钟就能拿到堆栈。祝排障愉快!
3万+

被折叠的 条评论
为什么被折叠?



