惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News and Events Feed by Topic
爱范儿
爱范儿
Blog — PlanetScale
Blog — PlanetScale
The GitHub Blog
The GitHub Blog
C
Check Point Blog
小众软件
小众软件
I
InfoQ
罗磊的独立博客
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Engineering at Meta
Engineering at Meta
酷 壳 – CoolShell
酷 壳 – CoolShell
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Hugging Face - Blog
Hugging Face - Blog
博客园 - 三生石上(FineUI控件)
MyScale Blog
MyScale Blog
The Cloudflare Blog
Last Week in AI
Last Week in AI
腾讯CDC
Y
Y Combinator Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
雷峰网
雷峰网
B
Blog
T
Tailwind CSS Blog
MongoDB | Blog
MongoDB | Blog
A
About on SuperTechFans
D
Docker
博客园 - 司徒正美
博客园_首页
Recent Announcements
Recent Announcements
D
DataBreaches.Net
阮一峰的网络日志
阮一峰的网络日志
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
G
Google Developers Blog
Microsoft Security Blog
Microsoft Security Blog
F
Fortinet All Blogs
Stack Overflow Blog
Stack Overflow Blog
aimingoo的专栏
aimingoo的专栏
N
Netflix TechBlog - Medium
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 聂微东
GbyAI
GbyAI
Jina AI
Jina AI
V
V2EX
Vercel News
Vercel News
IT之家
IT之家
WordPress大学
WordPress大学
M
MIT News - Artificial intelligence
NISL@THU
NISL@THU
V
Visual Studio Blog
C
Cybersecurity and Infrastructure Security Agency CISA

博客园 - 一线码农

记一次 .NET 某智慧医保云服务Linux 非托管泄露分析 .NET 高级调试技术:超越基础 Dump 分析 .NET 生产环境调试实战指南 记一次 .NET 某电力后台监控系统 内存暴涨分析 记一次 .NET 某注塑模具系统 CPU爆高分析 记一次 .NET 某集群管理软件 内存暴涨分析 记一次 .NET 某智慧工厂视觉程序 崩溃分析 记一次 .NET 某低代码开发框架 内存暴涨分析 记一次 .NET 某MES上位机拍照系统 内存暴涨分析 记一次 .NET 某RFID标签打印客户端 崩溃分析 对 .NET FileSystemWatcher引发内存碎片化的 反思 DotMemory系列:5. 如何实现自动化抓取和应用自托管 DotMemory系列:4. 如何分析进程的转储文件 DotMemory系列:3. 堆碎片化引发的内存暴涨分析 DotMemory系列:2. 事件泄露引发的内存暴涨分析 DotMemory系列:1. 终结队列积压引发的内存暴涨分析 记一次 .NET 某理财管理客户端 OOM溢出分析 记一次 .NET 某医联体管理系统 崩溃分析 记一次 .NET 某光放测试系统 崩溃分析 记一次 .NET 某药品缺陷高速检测系统 卡慢分析 聊一聊 .NET超高内存故障分析方法 的反思 记一次 .NET 某企业ECM内容管理系统 内存暴涨分析 记一次 .NET 某跨境物流系统 内存暴涨分析 记一次 .NET 某中医药附属医院门诊系统 崩溃分析 聊一聊 .NET 中的 CompositeChangeToken 聊一聊 .NET 中的 CancellationTokenSource 记一次 .NET 某CRM物流行业管理系统 崩溃分析
记一次 .NET 某光电成像后端解析系统 内存暴涨分析
一线码农 · 2026-07-16 · via 博客园 - 一线码农

一:背景

1. 讲故事

前些时间有位朋友找到我,说他们部署在linux上的系统内存占用比较高,领导让他找原因,由于自身的技术上限一直没找到,让我帮忙看下,让朋友通过 dotnet-dump 或者 procdump 从生产上抓一个下来,既然dump到手,那就进入分析环节啦。

二:内存暴涨分析

1. 为什么会暴涨

由于是linux上的.net,原来的 !address -summary 就彻底失去功效了,使用高版本的sos自带的全平台替换版 !maddress 去搞定,输出结果如下:


0:000> !maddress
0:000> !maddress
 +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 
 | Memory Kind            |        StartAddr |        EndAddr-1 |         Size | Type        | State       | Protect                | Image                                                   | 
 +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 
| Stack                  |     ffc81c7a0000 |     ffc81cfa0000 |       8.00mb | MEM_PRIVATE | MEM_COMMIT  | PAGE_READWRITE         |                                                         | 
 | Stack                  |     ffc81cfb0000 |     ffc81d7b0000 |       8.00mb | MEM_PRIVATE | MEM_COMMIT  | PAGE_READWRITE         |                                                         | 
 | Stack                  |     ffc81e7e0000 |     ffc81efe0000 |       8.00mb | MEM_PRIVATE | MEM_COMMIT  | PAGE_READWRITE         |                                                         | 
 | Stack                  |     ffc81eff0000 |     ffc81f7f0000 |       8.00mb | MEM_PRIVATE | MEM_COMMIT  | PAGE_READWRITE         |                                                         | 
 +-------------------------------------------------------------------------+ 
 | Memory Type            |          Count |         Size |   Size (bytes) | 
 +-------------------------------------------------------------------------+ 
 | Stack                  |            314 |       3.65gb |  3,918,856,192 | 
 | GCHeap                 |            384 |       3.02gb |  3,240,099,840 | 
 | PAGE_READWRITE         |          1,151 |     175.37mb |    183,889,536 | 
 | Image                  |            622 |     115.80mb |    121,428,352 | 
 | LowFrequencyHeap       |             33 |       6.31mb |      6,619,136 | 
 | HighFrequencyHeap      |             10 |       6.19mb |      6,488,064 | 
 | LoaderCodeHeap         |              7 |       5.00mb |      5,242,880 | 
 | PAGE_READONLY          |            131 |       4.26mb |      4,466,176 | 
 | PAGE_EXECUTE_WRITECOPY |              7 |     704.00kb |        720,896 | 
 | ResolveHeap            |              1 |     512.00kb |        524,288 | 
 | DispatchHeap           |              1 |     384.00kb |        393,216 | 
 | StubHeap               |              1 |     192.00kb |        196,608 | 
 | IndirectionCellHeap    |              1 |      64.00kb |         65,536 | 
 | CacheEntryHeap         |              1 |      64.00kb |         65,536 | 
 | LookupHeap             |              1 |      64.00kb |         65,536 | 
 | HostCodeHeap           |              1 |      64.00kb |         65,536 | 
 +-------------------------------------------------------------------------+ 
 | [TOTAL]                |          2,666 |       6.97gb |  7,489,187,328 | 
 +-------------------------------------------------------------------------+ 

看到卦中的这些信息,秤砣终于落地了,总计吃了6.97G的内存,主要都是被 Stack 和 GCHeap 给吃掉了。

2. 为什么 Stack 吃了这么多

熟悉 linux 的朋友应该知道,默认情况下 Stack 会吃掉 8M 的内存,这在上面的 items 上也能看出究竟,不过有点讽刺的是在微软的官方MSDN上居然没找到 DOTNET_DefaultStackSize 变量的解释,只在一些野路子中发现了端倪,下面图中小哥的大胡子我咋看起来 quite sexual, 截图如下:

image
image

搞清楚前因后果之后,处理起来就比较简单了,使用环境变量即可,不过这东西也有自己的限制,设置不了主线程

DOTNET_DefaultStackSize=180000

如果将StackSize从 8M -> 1.5M,那 Stack 所侵占的内存就彻底搞定啦。

3. 为什么 GCHeap 吃了这么多

要想找到这个问题的答案,直接 !eeheap -gc 观察托管堆即可。


0:000> !eeheap -gc

DATAS = 
========================================
Number of GC Heaps: 128
----------------------------------------
...
Heap 126 (0000aaacefcfe750)
generation 0 starts at ffed4400c468
generation 1 starts at ffed4400c450
generation 2 starts at ffed44000000
ephemeral segment allocation context: none
Small object heap
         segment            begin        allocated        committed allocated size       committed size      
    ffed43ff0000     ffed44000000     ffed4400c480     ffed44f80000 0xc480 (50304)       0xf80000 (16252928) 
Large object heap starts at fff5a4000000
         segment            begin        allocated        committed allocated size       committed size      
    fff5a3ff0000     fff5a4000000     fff5a44da1e8     fff5a4590000 0x4da1e8 (5087720)   0x590000 (5832704)  
Pinned object heap starts at fffda4000000
         segment            begin        allocated        committed allocated size       committed size      
    fffda3ff0000     fffda4000000     fffda4000018     fffda4010000 0x18 (24)            0x10000 (65536)     
------------------------------
Heap 127 (0000aaacefd2fc70)
generation 0 starts at ffed84028fe8
generation 1 starts at ffed84028d90
generation 2 starts at ffed84000000
ephemeral segment allocation context: none
Small object heap
         segment            begin        allocated        committed allocated size       committed size      
    ffed83ff0000     ffed84000000     ffed84029000     ffed84fa0000 0x29000 (167936)     0xfa0000 (16384000) 
Large object heap starts at fff5b4000000
         segment            begin        allocated        committed allocated size       committed size      
    fff5b3ff0000     fff5b4000000     fff5b4668240     fff5b4810000 0x668240 (6718016)   0x810000 (8454144)  
Pinned object heap starts at fffdb4000000
         segment            begin        allocated        committed allocated size       committed size      
    fffdb3ff0000     fffdb4000000     fffdb4004030     fffdb4010000 0x4030 (16432)       0x10000 (65536)     
------------------------------
GC Allocated Heap Size:    Size: 0x3577ef58 (897052504) bytes.
GC Committed Heap Size:    Size: 0xc1200000 (3240099840) bytes.

从卦中数据看,有经验的朋友马上就条件反射出这是个啥问题,对,我经常说的内存黑洞,即用handful的数据直接撬开了数十倍甚至数百倍的自身内存,比如上面 897M 撬开了 3.24G。

虽然程序中有快进快出的现象,但这里也有一个不可忽视的客观条件,即程序中居然有 128 个heap,这就说明机器是 128core的,哈哈,这么多核心的cpu,我估计内存也不小,一查果然是 254G 内存,某些行业真的是高富帅哈。。。输出如下:


0:000> dp libcoreclr!SVR::gc_heap::total_physical_mem L1
0000fffe`4b0b71f8  0000003f`88bc0000
0:000> ? 0000003f`88bc0000 /0n1024/0n1024/0n1024
Evaluate expression: 254 = 00000000`000000fe

再次观察托管堆你会发现 allocated sizecommitted size 相距不像我之前分析的程序会有那么大,这里也仅仅10多M左右,结果坏就坏在核心数太多,直接翻了128倍,导致了最后的的悲剧发生。

所以这个问题的解决思路也比较简单,两个路子。

  • 升级到 .NET10,使用自带的 DATAS 机制。
  • 修改 GC 为 Workstation 模式。

说实话这个内存黑洞在 .NET6 时代时,我在分析dump时就多次遇到,向gc架构师 Maoni 大佬吐槽过,终于这个问题在 .NET8 中得到了很大的重视,并在 .NET10 中默认开启。

三:总结

这次内存爆高的事故更多的还是 Linux 和 .NET 的内部设计机制综合导致的,了解这些 externel component 的性格秉性,对我们调试师非常重要。

图片名称