记一次ASP.NET CORE线上内存溢出问题与dotnet-dump的排查方法
这周系统更新了一个版本,部署到线上.
客户反馈整个系统全部都卡顿,随即我们上服务器检查
发现整个服务器内存竟然达到了20-30G的占用..如图:

其中有一个订单服务,独自占用13-18G内存,
当它重启以后,内存会降低下来一段时间,但过不了多久 就又会增长上去
高度怀疑出现了内存溢出的情况,由于是线上服务器而且是离线内网.
项目又都运行在docker容器中,容器为了最小化,采用了极简的系统,几乎任何常见命令都没有.
所以考虑采用挂载额外辅助容器的形式进行调试.
1.创建调试用的辅助容器
这个简单,我们直接创建DockerFile如下:
# 使用 .NET 5 SDK 作为基础镜像
FROM mcr.microsoft.com/dotnet/sdk:5.0 # 安装 dotnet-dump 、 dotnet-stack 、dotnet-counters、dotnet-trace的旧版本(兼容 .NET 5)
RUN dotnet tool install -g dotnet-dump --version 5.0.220101 \
&& dotnet tool install -g dotnet-stack --version 1.0.130701 \
&& dotnet tool install -g dotnet-counters --version 5.0.251802 \
&& dotnet tool install -g dotnet-trace --version 5.0.251802 \
&& apt-get update \
&& apt-get install -y unzip procps \
&& echo 'export PATH="$PATH:/root/.dotnet/tools"' >> /root/.bashrc #设定全局工具环境变量
ENV PATH="${PATH}:/root/.dotnet/tools" # 默认启动 bash 以方便交互
CMD ["/bin/bash"]
这里我们使用当前系统RunTime对应版本的SDK作为基础镜像.
然后安装我们调试程序信息需要的工具:dotnet-dump 、 dotnet-stack 、dotnet-counters、dotnet-trace
先介绍一下这些工具.
dotnet-dump:可以收集和分析 Windows、Linux 和 macOS dump的信息,可以运行 SOS 命令来分析崩溃和垃圾回收器 (GC)。
dotnet-stack:可以收集 .NET 进程中的所有线程捕获和打印托管堆栈。
dotnet-counters:是一个性能监视工具,可以临时监视.NET程序的运行状况和做一些初级的性能调查
dotnet-trace:在不使用本机探查器的情况下对正在运行的.NET Core 进程进行跟踪
2.将辅助调试容器附加到应用容器运行
首先我们需要重新run一个应用容器,并给它特权模式和系统级的调试权限,大概命令如下:
docker run -d --name myapp --privileged=true --cap-add=SYS_PTRACE -e ASPNETCORE_ENVIRONMENT=dev -e COMPlus_EnableDiagnostics=1 --volume /home/tmp:/tmp order-test:5.0
重点是privileged参数和cap-add参数,还有应用系统的tmp文件夹需要映射到宿主机
然后我们运行我们的调试容器并附加到应用容器
docker run -it --rm --cap-add=SYS_PTRACE --pid=container:myapp --privileged=true --volume /home/tmp:/tmp dotnet-debug-tools:5.0
同样,它也需要privileged参数和cap-add参数,tmp临时文件也需要映射在宿主机和应用容器同样的目录下
注意--pid=container:myapp 中的myapp 是上面应用容器的名称.
然后,我们在调试容器直接运行命令:
ps aux
应该就能看到应用服务中的dotnet的进程了.因为在容器中运行,所以一般dotnet的PID是1,如图:

3.分析应用容器内dotnet进程的情况.
我们可以先使用dotnet-counters进行监控.
命令如下,其中-p是dotnet的进程编号:
dotnet-counters monitor -p 1
能得到如下结果:
% Time in GC since last GC (%) 0
Allocation Rate (B / 2 sec) 98,016
CPU Usage (%) 0
Exception Count (Count / 2 sec) 0
GC Fragmentation (%) 8.189
GC Heap Size (MB) 11,419
Gen 0 GC Count (Count / 2 sec) 0
Gen 0 Size (B) 192
Gen 1 GC Count (Count / 2 sec) 0
Gen 1 Size (B) 36,742,336
Gen 2 GC Count (Count / 2 sec) 0
Gen 2 Size (B) 8.8066e+09
IL Bytes Jitted (B) 7,248,623
LOH Size (B) 3.3414e+09
Monitor Lock Contention Count (Count / 2 sec) 0
Number of Active Timers 209
Number of Assemblies Loaded 426
Number of Methods Jitted 135,063
POH (Pinned Object Heap) Size (B) 1,299,832
ThreadPool Completed Work Item Count (Count / 2 sec) 0
ThreadPool Queue Length 0
ThreadPool Thread Count 12
Working Set (MB) 14,910
我们可以直接借助AI分析这个性能指标如下:
| 指标 | 当前值 | 分析建议 |
|---|---|---|
| CPU Usage (%) | 0 |
CPU 几乎未被使用,说明当前应用 处于空闲或阻塞状态。如果此时预期它应该在处理请求,说明可能卡在 I/O、锁、数据库等等待资源上。 |
| GC Heap Size (MB) | 11,419 MB |
堆内存非常大(约 11GB),说明分配对象多或存在内存泄漏风险。建议配合 dump 分析对象分布。 |
| GC Fragmentation (%) | 8.189% |
碎片率较低,尚可接受。通常低于 20% 问题不大。 |
| LOH Size (B) | ~3.34 GB |
Large Object Heap(LOH)占用了非常大的空间。意味着有很多大对象(如数组、字符串、缓存等)未被及时回收,或持续增长。需要进一步 dump 分析。 |
| Gen 2 Size (B) | ~8.8 GB |
Gen2 表示长时间存活对象。此值非常高,说明内存中存在大量老对象未被释放,极可能存在内存泄漏。 |
| Gen 0/1 GC Count | 0 |
说明当前没有发生 GC,或者是刚启动。这种情况下内存持续增长会导致最终触发 GC 或 OOM。 |
| % Time in GC since last GC | 0% |
目前没有 GC 时间消耗,结合上面 GC Count 是 0,一致。 |
| Working Set (MB) | 14,910 MB |
进程实际占用物理内存约 14GB,结合堆大小与 LOH/Gen2 数值合理,但这也是很大的内存占用,需要关注增长趋势。 |
| Allocation Rate | 98 KB / 2 sec |
内存分配速率很低(接近空闲),说明当前没有明显的内存增长。 |
| ThreadPool Thread Count | 12 |
线程池线程数正常范围,无需担心。 |
| ThreadPool Queue Length | 0 |
没有待处理任务,说明任务执行速度没有瓶颈。 |
| Exception Count | 0 |
无异常抛出,良好。 |
| Monitor Lock Contention Count | 0 |
没有锁争用,说明线程间竞争不激烈。 |
根据AI的分析报告,我们可以得知:
GC Heap Size (MB) 堆内存极大达到了11G
LOH Size (B)大对象指标也很大,有3G的大对象
Gen2 长期活动的对象很多,占用了8G
这样我们基本就可以断定是在应用中出现了内存泄漏的情况.
也不用额外在看别的信息了. 我们直接使用dotnet-dump 抓取内存快照进行分析,抓取命令如下:
dotnet-dump collect -p 1
我们会得到如下结果:
Writing minidump with heap to ./core_20250514_030614
Complete
由于内存快照比较大,复制回来分析..难度比较高.我们可以直接继续利用dotnet-dump analyze 在线上分析
执行命令如下:
dotnet-dump analyze core_20250514_030614
然后我们就可以使用sos命令进行分析了.
既然是内存泄漏,我们直接查看托管堆里面的到底是啥情况,命令如下:
dumpheap -stat
正常是按从小到大排序的..所以很尬,我们划到最下面,看到如下结果:

由于我比较清楚这个代码的情况,直接发现了一个很明显的问题,
Order.OperApply.ApplyVoucherDetail 只是一个业务实体而已,但是在堆里面有29W个对象,
明显是很不合理的.
我们直接分析它.可以使用dumpheap -mt 获取它的所有实列地址.
dumpheap -mt 00007f117f67e590
可以得到如下结果:

我们选择最后一个Adderss寻找对象的根方法.命令如下:
gcroot 00007f0ca9b55818
可以看到如下代码内容(由于太多,我只贴出来有用的):
-> 00007F0E643FB770 RabbitMQ.Client.Impl.AsyncConsumerWorkService
-> 00007F0DA4D0BBB8 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[StockManage.Handler.ProductInOutStockStatDistributedHandler+<HandleEventAsync>d__6, StockManage.Application]]
-> 00007F0AFCCE9020 System.Collections.Generic.Dictionary`2+Entry[[System.Object, System.Private.CoreLib],[Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry, Microsoft.EntityFrameworkCore]][]
-> 00007F1035A778A0 Order.OperApply.ApplyVoucher
这四条,我们就可以看出来,是RabbitMQ的消息队列,在ProductInOutStockStatDistributedHandler方法,进行消费的时候
会通过EF CORE 创建这个ApplyVoucher的实例,接下来,我们就需要查看这个ProductInOutStockStatDistributedHandler,到底做了什么.
4.排查代码
直接去查看这个方法的代码,发现竟然没有任何一处使用了ApplyVoucher实体.
所以,我们直接运行本地调试,发现在这个方法结束后会去查询ApplyVoucher表.
分析代码后,我们发现,由于我们使用的是ABP的框架,在方法结束后,会自动写入审计日志,最后才会结束整个调用.
遂调查审计日志模块,发现有小伙伴在审计日志中对ABP的AuditLogInfo对象进行了序列化操作.
查询ABP源码发现,AuditLogInfo的EntityChanges中竟然储存了Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry对象
而EntityEntry对象又包含了整个DBContext上下文..然而我们的DBContext 又开启了懒加载的功能
所以当它被序列化的时候...等于在序列化整个数据库..(这里省略一百个C...)
赶紧屏蔽这段代码,并更新到线上. 问题瞬间解决了..
5.深入求证.
解决问题后,还是比较好奇,有没有同样使用ABP的兄弟遇见相关问题,遂去查询abp源码仓库..
竟然发现在Extracting a Module as a Microservice的相关说明里.
看到了这一段...

而且ABP框架还特意创建了一个IAuditLogInfoToAuditLogConverter来转换AuditLogInfo对象..方便后面进行序列化和存储..
这一次分析线上问题的过程,还是比较有参考性的,所以记录一下.也希望对各位兄弟们有帮助.觉得OK的可以点个推荐~~.3Q~
记一次ASP.NET CORE线上内存溢出问题与dotnet-dump的排查方法的更多相关文章
- 记一次asp.net core 线上崩溃解决总结
1.首先要先准备好环境,安装lldb 工具 要安装3.9版本的,因为每个版本对应dnc版本不一样,3.9的支持2.2 版本,然后确定分析的机器里dnc 版本和线上的生产环境是否一致,自己安装比较费劲, ...
- ASP.NET Core文件上传IFormFile于Request.Body的羁绊
前言 在上篇文章深入探究ASP.NET Core读取Request.Body的正确方式中我们探讨了很多人在日常开发中经常遇到的也是最基础的问题,那就是关于Request.Body的读取方式问题,看是简 ...
- C# 6 与 .NET Core 1.0 高级编程 - 40 ASP.NET Core(上)
译文,个人原创,转载请注明出处(C# 6 与 .NET Core 1.0 高级编程 - 40 章 ASP.NET Core(上)),不对的地方欢迎指出与交流. 章节出自<Professiona ...
- [转载]ASP.NET Core文件上传与下载(多种上传方式)
ASP.NET Core文件上传与下载(多种上传方式) 前言 前段时间项目上线,实在太忙,最近终于开始可以研究研究ASP.NET Core了. 打算写个系列,但是还没想好目录,今天先来一篇,后面在 ...
- 放码来战!HMS Core线上Codelabs挑战赛正式开始
亲爱的开发者,在1024程序员节即将到来之际,HMS Core准备了一场线上Codelabs挑战赛,现向你发出诚挚邀请,希望你能将新奇的想法和对产品的思考融入代码,用技术与世界对话. HMS Core ...
- 码上来战!探索“智”感生活,HMS Core线上Codelabs挑战赛第4期开始!
HMS Core线上Codelabs挑战赛第4期正式开始!我们向所有实践力超强.创新力满满的开发者发出邀请,用你的超级"码"力,解锁更多应用价值! 生活里,我们被手机"秒 ...
- ASP.NET Core - 缓存之内存缓存(上)
1. 缓存 缓存指的是在软件应用运行过程中,将一些数据生成副本直接进行存取,而不是从原始源(数据库,业务逻辑计算等)读取数据,减少生成内容所需的工作,从而显著提高应用的性能和可伸缩性,使用好缓存技术, ...
- ASP.NET Core - 缓存之内存缓存(下)
话接上篇 [ASP.NET Core - 缓存之内存缓存(上)],所以这里的目录从 2.4 开始. 2.4 MemoryCacheEntryOptions MemoryCacheEntryOption ...
- 记一次asp.net core 在iis上运行抛出502.5错误
asp.net core 在iis上运行抛出502.5异常的部分原因以及解决方案 环境说明 已安装 .net core runtime 2.1.401 已安装 .net core windows ho ...
- ASP.NET Core 文件上传
前言 上篇博文介绍了怎么样在 asp.net core 使用 Redis 和 Protobuf 进行 Session缓存.本篇的是开发过程中使用的一个小功能,怎么做单文件和多文件上传. 如果你觉得对你 ...
随机推荐
- 发那科FANUC机器人A06B-0652-B212电机维修基本流程
发那科FANUC机器人以其卓越的性能和可靠性赢得了广泛的认可.然而,就像其他任何机械设备一样,长时间的运行和复杂的工作环境都可能使伺服电机面临维修的需求.为了确保您的发那科FANUC机器人A06B-0 ...
- Ubuntu 部署饥荒联机版服务器 Linux DST_Dedicate_Server
0. 文件夹 - ~ |- ~/steamcmd # 装的是steamcmd_linux.tar.gz以及其解压出来的东西 |- ~/DST # 装的是DST服务器可执行文件.世界存档.世界模板 |- ...
- 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
你好呀,我是歪歪. 事情是这样的,前几天有一个读者给我发消息,说他面试的时候遇到一个奇形怪状的面试题. 歪师傅纵横面试界多年,最喜欢的是奇形怪状的面试题. 可以说是见过大场面的人,所以让他描述一下具体 ...
- Nginx 拒绝错误SNI请求以防止源站被扫描
Nginx 1.19.4 版本更新了一个新的配置,允许使用 ssl_reject_handshake 这个参数来拒绝错误 SNI 请求的握手,可以防止被类似Censys互联网扫码工具扫描出源站ip 在 ...
- Python异步编程进阶指南:破解高并发系统的七重封印
title: Python异步编程进阶指南:破解高并发系统的七重封印 date: 2025/2/25 updated: 2025/2/25 author: cmdragon excerpt: 本文是异 ...
- STM32实战——ESP8266 WIFI模块
ESP8266 硬件介绍 ESP8266系列模组有哪些: 在本实验中,ESP8266与ESP-01不做区分. ESP-01引脚介绍: 引脚 功能 3.3 3.3V供电,避免使用5V供电 RX UART ...
- django4.2 与python 最新对应关系表
- Qt Qss 设置QPushButton图标和背景
文章目录 Qt QSS 设置 QPushButton的图标叠加背景 前言 解决方法 background-repeat.background-position 最终样式 Qt QSS 设置 QPush ...
- Qt QHeaderView 添加复选框
QT QTableView 表头添加复选框 最近在做表格,用QTableView,然后有一个需求是给表格添加表头,但是没有一个API能够在表头添加复选框,基本都是来重载QHeaderView,有两种方 ...
- go minio 设置访问权限
bucket 权限 桶默认可以有三种 Access Policy 策略:public.custom.private. public:不经过任何认证可以直接访问资源 custom:自定义策略 Acces ...