Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >记一次ASP.NET CORE线上内存溢出问题与dotnet-dump的排查方法

记一次ASP.NET CORE线上内存溢出问题与dotnet-dump的排查方法

作者头像
GuZhenYin
发布于 2025-05-17 01:43:54
发布于 2025-05-17 01:43:54
17200
代码可运行
举报
文章被收录于专栏:GuZhenYinGuZhenYin
运行总次数:0
代码可运行

前言

这周系统更新了一个版本,部署到线上.

客户反馈整个系统全部都卡顿,随即我们上服务器检查

发现整个服务器内存竟然达到了20-30G的占用..如图:

其中有一个订单服务,独自占用13-18G内存,

当它重启以后,内存会降低下来一段时间,但过不了多久 就又会增长上去

高度怀疑出现了内存溢出的情况,由于是线上服务器而且是离线内网.

项目又都运行在docker容器中,容器为了最小化,采用了极简的系统,几乎任何常见命令都没有.

所以考虑采用挂载额外辅助容器的形式进行调试.

正文

1.创建调试用的辅助容器

这个简单,我们直接创建DockerFile如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 使用 .NET 5 SDK 作为基础镜像
FROM mcr.microsoft.com/dotnet/sdk:5.0

# 安装 dotnet-dump 、 dotnet-stack 、dotnet-counters、dotnet-trace的旧版本(兼容 .NET 5RUN 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:可以收集和分析 WindowsLinux 和 macOS dump的信息,可以运行 SOS 命令来分析崩溃和垃圾回收器 (GC)。

dotnet-stack:可以收集 .NET 进程中的所有线程捕获和打印托管堆栈。

dotnet-counters:是一个性能监视工具,可以临时监视.NET程序的运行状况和做一些初级的性能调查

dotnet-trace:在不使用本机探查器的情况下对正在运行的.NET Core 进程进行跟踪

2.将辅助调试容器附加到应用容器运行

首先我们需要重新run一个应用容器,并给它特权模式和系统级的调试权限,大概命令如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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文件夹需要映射到宿主机

然后我们运行我们的调试容器并附加到应用容器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 是上面应用容器的名称.

然后,我们在调试容器直接运行命令:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ps aux

应该就能看到应用服务中的dotnet的进程了.因为在容器中运行,所以一般dotnet的PID是1,如图:

3.分析应用容器内dotnet进程的情况.

我们可以先使用dotnet-counters进行监控.

命令如下,其中-p是dotnet的进程编号:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
dotnet-counters monitor -p 1

能得到如下结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
% 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 抓取内存快照进行分析,抓取命令如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
dotnet-dump collect -p 1

我们会得到如下结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Writing minidump with heap to ./core_20250514_030614
Complete

由于内存快照比较大,复制回来分析..难度比较高.我们可以直接继续利用dotnet-dump analyze 在线上分析

执行命令如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
dotnet-dump analyze core_20250514_030614

然后我们就可以使用sos命令进行分析了.

既然是内存泄漏,我们直接查看托管堆里面的到底是啥情况,命令如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
dumpheap -stat

正常是按从小到大排序的..所以很尬,我们划到最下面,看到如下结果:

 由于我比较清楚这个代码的情况,直接发现了一个很明显的问题,

Order.OperApply.ApplyVoucherDetail  只是一个业务实体而已,但是在堆里面有29W个对象,

明显是很不合理的.

我们直接分析它.可以使用dumpheap -mt 获取它的所有实列地址.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
dumpheap -mt 00007f117f67e590

可以得到如下结果:

 我们选择最后一个Adderss寻找对象的根方法.命令如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
gcroot 00007f0ca9b55818

可以看到如下代码内容(由于太多,我只贴出来有用的):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
->  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源码发现,AuditLogInfoEntityChanges中竟然储存了Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry对象

EntityEntry对象又包含了整个DBContext上下文..然而我们的DBContext 又开启了懒加载的功能

所以当它被序列化的时候...等于在序列化整个数据库..(这里省略一百个C...)

赶紧屏蔽这段代码,并更新到线上. 问题瞬间解决了..

5.深入求证.

解决问题后,还是比较好奇,有没有同样使用ABP的兄弟遇见相关问题,遂去查询abp源码仓库..

竟然发现在Extracting a Module as a Microservice的相关说明里.

看到了这一段...

而且ABP框架还特意创建了一个IAuditLogInfoToAuditLogConverter来转换AuditLogInfo对象..方便后面进行序列化和存储..

后记

这一次分析线上问题的过程,还是比较有参考性的,所以记录一下.也希望对各位兄弟们有帮助.觉得OK的可以点个推荐~~.3Q~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-05-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
调试 .NET Core 中的内存泄漏
当应用引用不再需要执行所需任务的对象时,可能会发生内存泄漏。 引用上述对象会使垃圾回收器无法回收所使用的内存,这通常会导致性能降低,并可能最终引发 OutOfMemoryException。
呆呆
2022/01/09
1.9K0
.NET周刊【5月第4期 2025-05-25】
https://www.cnblogs.com/hez2010/p/18889954/the-new-satori-gc-for-dotnet
InCerry
2025/06/09
200
.NET周刊【5月第4期 2025-05-25】
浅入 .NET Core 中的内存和GC知识
【1】https://docs.microsoft.com/zh-cn/dotnet/standard/managed-code
痴者工良
2021/04/26
7320
《快来为你的 .NET 应用加个监控吧!》更新版本啦
CZGL.ProcessMetrics 是一个 Metrics 库,能够将程序的 GC、CPU、内存、机器网络、磁盘空间等信息记录下来,使用 Prometheus 采集信息,然后使用 Grafana 显示。
痴者工良
2021/07/20
5290
《快来为你的 .NET 应用加个监控吧!》更新版本啦
.NET Core 调试 CPU 爆高问题
在实际开发和生产环境中,.NET Core 应用程序遇到 CPU 使用率飙升的问题并不少见。CPU 高负载会直接影响应用程序的性能,进而影响用户体验。因此,及时识别并解决 CPU 爆高问题是开发者需要掌握的关键技能。
Michel_Rolle
2024/12/24
2.3K0
调试 .NET Core 中的高 CPU 使用率
本教程将介绍如何调试 CPU 使用率过高的情况。 使用提供的示例 ASP.NET Core Web 应用 源代码存储库,可以故意造成死锁。 终结点将停止响应并遇到线程累积问题。 你将了解如何使用各种工具,通过几条关键的诊断数据诊断此情况。
呆呆
2022/01/09
1.4K0
.NET 9 中的 RuntimeMetrics
.NET 9 中引入了 RuntimeMetrics,基于 dotnet 里的 metrics 实现 System.Diagnostic.Metrics.Meter 来生成 metrics 数据,包含了 CPU、内存、GC、JIT 以及线程等信息
JusterZhu
2025/01/23
630
.NET 9 中的 RuntimeMetrics
收集指标
本文适用范围:✔️ .NET Core 3.1 及更高版本 ✔️ .NET Framework 4.6.1 及更高版本
呆呆
2022/01/07
6620
记一次 .NET 某风控管理系统 内存泄漏分析
上个月中旬,星球里的一位朋友在微信找我,说他的程序跑着跑着内存会不断的缓慢增长并无法释放,寻求如何解决 ?
玖柒的小窝
2021/11/02
5200
记一次 .NET 某风控管理系统 内存泄漏分析
讨论.NET Core 配置对GC 工作模式与内存的影响
https://mp.weixin.qq.com/s/PqhUzvFpzopU7rVRgdy7eg
yoyofx
2018/09/05
3.4K1
使用dotnet-monitor sidecar模式 dump docker运行的dotnet程序.
随着容器和云技术的发展, 大量的应用运行在云上的容器中, 它们的好处是毋庸置疑的, 例如极大的提高了我们的研发部署速度, 快速的扩缩容等等, 但是也存在一些小小的问题, 例如难以调试. 基于VM的部署我们可以通过安全的方式登录到主机上做一些你想做的事情, 但是云上的容器那就是不太方便了(目前AWS的ECS已经有类似docker exec的方式直接进入容器中了, 其他的云未作了解). 但是就算能进入容器也不意味着调试就好做了, 通常来说使用的镜像都是经过优化和精简的(如果要调式可能需要安装大量的组件).
旺财的城堡
2022/11/02
1.3K0
使用dotnet-monitor sidecar模式 dump docker运行的dotnet程序.
一次完整的JVM堆外内存泄漏故障排查记录
记录一次线上JVM堆外内存泄漏问题的排查过程与思路,其中夹带一些「JVM内存分配的原理分析」以及「常用的JVM问题排查手段和工具分享」,希望对大家有所帮助。
Rude3Knife的公众号
2020/08/28
4K0
一次完整的JVM堆外内存泄漏故障排查记录
Linux命令-查看内存、GC情况及jmap 用法
首先可以通过ps命令找到进程id,比如 ps -ef | grep kafka 可以看到kafka这个程序的进程id
chenchenchen
2020/05/27
12.3K0
详解 Java 线上问题排查思路
因为通常线程数量会由线程池管理,一般不会超过我们设定的最大值;而线程“死锁”通常是人为代码问题,某个获得锁的线程没有释放锁,导致其他线程一直处于 Waiting 状态(或者 CAS 自旋状态)。
宫水三叶的刷题日记
2021/03/02
3.5K0
详解 Java 线上问题排查思路
记一次内存溢出问题的排查、分析过程及解决思路
这个测试工具的开发已有一段时间了,由于数据量过大,写入数据较慢,导致工具执行耗时较长,所以再次优化了实现方案,进行二阶段的程序开发。
软件测试君
2020/09/23
2.6K0
记一次内存溢出问题的排查、分析过程及解决思路
记一次敖丙dubbo线程池事故排查
我写过dubbo系列的文章,大家看完这章后想了解更多dubbo细节可以查看往起文章:
敖丙
2021/04/16
1.1K0
记一次敖丙dubbo线程池事故排查
【日活百万电商返利App】一次线上JVM问题定位排查
查看进程使用gc情况: jstat -gc 16969<pid> 5000(打印时间间隔)
用户2032165
2020/03/27
9680
【日活百万电商返利App】一次线上JVM问题定位排查
ASP.NET Core 性能优化最佳实践
本文提供了 ASP.NET Core 的性能最佳实践指南。 译文原文地址:https://docs.microsoft.com/en-us/aspnet/core/performance/perfor
newbe36524
2020/09/14
2.7K0
ASP.NET Core 性能优化最佳实践
架构师技能3-彻底深入理解和分析Java中内存溢出OutOfMemoryError
java开发人员经常遇到OutOfMemoryError的问题。要解决这些问题,要有对java虚拟机的内存管理有一定的认识,甚至对linux系统也要有一定的熟悉。透过分析问题,深入挖掘问题本质,进而强迫自己学习相应基础知识。
黄规速
2022/04/14
4820
架构师技能3-彻底深入理解和分析Java中内存溢出OutOfMemoryError
体验了一把线上CPU100%及应用OOM的排查和解决过程
项目中默认使用 spring-cloud-sleuth-zipkin 依赖得到 zipkin-reporter。分析的版本发现是 zipkin-reporter版本是 2.7.3 。
全栈程序员站长
2022/07/20
5230
体验了一把线上CPU100%及应用OOM的排查和解决过程
相关推荐
调试 .NET Core 中的内存泄漏
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验