如何定位 linux 环境 .net 进程资源占用高的程序代码

如何在 Linux 系统上定位 .NET 进程 “资源飙高” 的代码行,可以分三步走:

  1. 先确认是哪个进程、哪类资源飙高
  2. .NET 专用工具抓取“热点”线程、调用栈
  3. 把栈还原到具体源码(行号)

下面给出最常用的 完整命令链 ,全部在 Linux 终端完成,无需 Windows 远程调试。

一、确认目标进程与资源类型

  1. 看实时排行
top   # 按 P 排 CPU,按 M 排内存,记录 PID

或更直观:

htop  # 方向键选中进程,F5 树状视图
  1. 只看 .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/toolsPATH 里:

export PATH="$PATH:$HOME/.dotnet/tools"

三、现场抓“热点”

1. 先开两个终端,一边实时监控,一边抓栈

终端 A:

dotnet-counters monitor -p $PID

默认每 1s 刷新,能看到 GC、CPU、线程池、异常数 等;
如果 CPU 飙高,继续第 2 步;
如果内存一路上涨,直接跳到第 4 步生成 dump

2. CPU 飙高——抓 30 sCPU 火焰图

终端 B:

dotnet-trace collect -p $PID --duration 00:00:30 --format speedscope -o cpu.speedscope

结束后把 cpu.speedscope 下载到本机,用 https://speedscope.app 打开,即可看到哪段 C# 代码占 CPU 百分比。

speedscope.app

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 均验证通过)。

脚本逻辑

  1. 找到当前 CPU 或内存占用最高的 dotnet 进程(可改成按内存);
  2. 把该进程的全部线程 CPU 占用列出来;
  3. dotnet-dump 抓线程堆栈(自带符号,直接定位到 C# 方法名、文件名、行号);
  4. 输出最耗 CPU10 条线程对应的堆栈片段,方便一眼看到是哪一行代码在“烧”资源;
  5. 所有中间文件落盘到 /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"

使用方法:

  1. 安装 dotnet-dump(一次即可):
dotnet tool install --global dotnet-dump
  1. 脚本赋权:
chmod +x dotnet_top.sh
  1. 运行(默认按 CPU,也可按内存):
sudo ./dotnet_top.sh        # CPU
sudo ./dotnet_top.sh mem    # 内存
  1. 结果示例(节选):
======================== 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:127ProcessBatch 方法在疯狂抢 CPU,接下来就去 review 这段代码即可。

脚本特点

  • 无需手工换算 16 进制 tid
  • 直接给出文件名、行号,定位到 C# 源码;
  • 100% 官方工具,生产环境可放心使用;
  • 中间文件全保留,方便事后二次挖掘。

把脚本丢到跳板机 /usr/local/bin,下次值班再遇到 “dotnet 吃 CPU” 三秒钟就能拿到堆栈。祝排障愉快!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ChaITSimpleLove

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值