记一次 .NET某游戏币自助机后端 内存暴涨分析

news/2024/10/4 17:27:34

一:背景

1. 讲故事

前些天有位朋友找到我,说他们的程序内存会偶发性暴涨,自己分析了下是非托管内存问题,让我帮忙看下怎么回事?哈哈,看到这个dump我还是非常有兴趣的,居然还有这种游戏币自助机类型的程序,下次去大玩家看看他们出币的机器后端是不是C#写的?由于dump是linux上的程序,刚好windbg可以全平台分析,太爽了,直接用windbg开干吧。

二:WinDbg 分析

1. 到底是哪里的泄漏

在 windows 平台上相信有很多朋友都知道用 !address -summary 命令看,但这是专属于windows平台的命令,在分析linux上的dump不好使,参考如下输出:


0:000> !address -summary--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
<unknown>                              1685     7ffc`d6725c00 ( 127.988 TB) 100.00%  100.00%
Image                                  7102        0`0b524400 ( 181.142 MB)   0.00%    0.00%--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal2248     7ffc`02549000 ( 127.984 TB)          100.00%
MEM_PRIVATE                            6539        0`df701000 (   3.491 GB)   0.00%    0.00%--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal2248     7ffc`02549000 ( 127.984 TB) 100.00%  100.00%
MEM_COMMIT                             6539        0`df701000 (   3.491 GB)   0.00%    0.00%--- Protect Summary (for commit) - RgnCount ----------- Total Size -------- %ofBusy %ofTotal
PAGE_READWRITE                         2099        0`dd75e000 (   3.460 GB)   0.00%    0.00%
PAGE_EXECUTE_WRITECOPY                   33        0`00d4c000 (  13.297 MB)   0.00%    0.00%
PAGE_READONLY                          2736        0`00b01000 (  11.004 MB)   0.00%    0.00%
PAGE_EXECUTE_READ                      1671        0`00756000 (   7.336 MB)   0.00%    0.00%--- Largest Region by Usage ----------- Base Address -------- Region Size ----------
<unknown>                                 0`00000000     55cb`2dc3b000 (  85.794 TB)
Image                                  7f71`9dbdd000        0`01b16000 (  27.086 MB)

卦中的内存段分类用处不大,也没有多大的参考价值,那怎么办呢?其实 coreclr 团队也考虑到了这个情况,它提供了一个 maddress 命令来实现跨平台的 !address,更改后输出如下:


0:000> !sos maddress
Enumerating and tagging the entire address space and caching the result...
Subsequent runs of this command should be faster.
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Memory Kind            |        StartAddr |        EndAddr-1 |         Size | Type        | State       | Protect                | Image                                                             | +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Stack                  |     7f6e356ec000 |     7f6e35eec000 |       8.00mb | MEM_PRIVATE | MEM_COMMIT  | PAGE_READWRITE         |                                                                   | | Stack                  |     7f6e35eed000 |     7f6e366ed000 |       8.00mb | MEM_PRIVATE | MEM_COMMIT  | PAGE_READWRITE         |                                                                   | | Stack                  |     7f6e366ee000 |     7f6e36eee000 |       8.00mb | MEM_PRIVATE | MEM_COMMIT  | PAGE_READWRITE         |                                                                   | | Stack                  |     7f6e36eef000 |     7f6e376ef000 |       8.00mb | MEM_PRIVATE | MEM_COMMIT  | PAGE_READWRITE         |                                                                   | ...+-------------------------------------------------------------------------+ | Memory Type            |          Count |         Size |   Size (bytes) | +-------------------------------------------------------------------------+ | Stack                  |            423 |       3.29gb |  3,528,859,648 | | Image                  |          7,102 |     181.14mb |    189,940,736 | | PAGE_READWRITE         |            206 |      89.18mb |     93,511,680 | | GCHeap                 |              3 |      37.75mb |     39,587,840 | | HighFrequencyHeap      |            395 |      24.66mb |     25,858,048 | | LowFrequencyHeap       |            316 |      22.20mb |     23,277,568 | | LoaderCodeHeap         |             13 |      17.00mb |     17,825,792 | | ResolveHeap            |              2 |     732.00kb |        749,568 | | HostCodeHeap           |              8 |     668.00kb |        684,032 | | DispatchHeap           |              1 |     196.00kb |        200,704 | | PAGE_EXECUTE_WRITECOPY |              6 |     184.00kb |        188,416 | | CacheEntryHeap         |              3 |     164.00kb |        167,936 | | IndirectionCellHeap    |              3 |     152.00kb |        155,648 | | LookupHeap             |              3 |     144.00kb |        147,456 | | StubHeap               |              2 |      76.00kb |         77,824 | | PAGE_EXECUTE_READ      |              1 |       4.00kb |          4,096 | +-------------------------------------------------------------------------+ | [TOTAL]                |          8,487 |       3.65gb |  3,921,236,992 | +-------------------------------------------------------------------------+ 

从卦中可以看到当前程序总计 3.65G 内存占用,基本上都被线程栈给吃掉了,更让人意想不到的是这个线程栈居然占用 8M 的内存空间,这个着实有点大了,而且 linux 不像 windows 有一个 reserved 的概念,这里的 8M 是实实在在的预占,可以观察这 8M 的内存地址即可,都是初始化的 0, 这就说不过去了。


0:000> dp 7f6e356ec000 7f6e35eec000
00007f6e`356ec000  00000000`00000000 00000000`00000000
...
00007f6e`35eebfc0  00000000`00000000 00000000`00000000
00007f6e`35eebfd0  00000000`00000000 00000000`00000000
00007f6e`35eebfe0  00000000`00000000 00000000`00000000
00007f6e`35eebff0  00000000`00000000 00000000`00000000

2. 如何修改栈空间大小

一般来说不同的操作系统发行版有不同的默认栈空间配置,可以先到内存搜一下当前是哪一个发行版,做法就是搜索操作系统名称主要关键字。


0:000> s-a 0 L?0xffffffffffffffff "centos"
...
000055cb`2ecf08c8  63 65 6e 74 6f 73 2e 37-2d 78 36 34 00 00 00 00  centos.7-x64....
...

从卦中可以看到当前操作系统是 centos7-x64,在 windows 平台上修改栈空间大小可以修改 PE 头,在 linux 上有两种做法。

  • 修改 ulimit -s 参数

root@ubuntu:/data# ulimit -s
8192
root@ubuntu:/data# ulimit -s 2048
root@ubuntu:/data# ulimit -s
2048
  • 修改 DOTNET_DefaultStackSize 环境变量

DOTNET_DefaultStackSize=180000

更多可以参考文章: https://www.alexander-koepke.de/post/2023-10-18-til-dotnet-stack-size/

上面是解决问题的第一个方向,接下来我们说另一个方向,为什么会产生总计 423 个线程呢?

3. 为什么会有那么多线程

要找到这个答案,需要去看每一个线程此时都在干嘛,这个可以使用 windbg 专属命令。


0:000> ~*e !clrstack
...
OS Thread Id: 0x4e (24)Child SP               IP Call Site
00007F70B20FC4B0 00007f71a4131ad8 [InlinedCallFrame: 00007f70b20fc4b0] /app/Confluent.Kafka.dll!Unknown
00007F70B20FC4B0 00007f7130299970 [InlinedCallFrame: 00007f70b20fc4b0] /app/Confluent.Kafka.dll!Unknown
00007F70B20FC4A0 00007f7130299970 ILStubClass.IL_STUB_PInvoke(IntPtr, IntPtr)
00007F70B20FC530 00007f7130309fab /app/Confluent.Kafka.dll!Unknown
00007F70B20FC880 00007f7131c5a75d /app/Confluent.Kafka.dll!Unknown
00007F70B20FC8A0 00007f7130303ebe /app/DotNetCore.CAP.Kafka.dll!Unknown
00007F70B20FC980 00007f71302f4854 /app/DotNetCore.CAP.dll!Unknown
00007F70B20FCA50 00007f7129b187f4 System.Threading.Tasks.Task.InnerInvoke() [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 2387]
00007F70B20FCA70 00007f7129b1d316 System.Threading.Tasks.Task+c.<.cctor>b__272_0(System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 2375]
00007F70B20FCA80 00007f7129b03d6b System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs @ 183]
00007F70B20FCAD0 00007f7129b18524 System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef, System.Threading.Thread) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 2333]
00007F70B20FCB50 00007f7129b18418 System.Threading.Tasks.Task.ExecuteEntryUnsafe(System.Threading.Thread) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 2271]
00007F70B20FCB70 00007f7129b21a67 System.Threading.Tasks.ThreadPoolTaskScheduler+c.<.cctor>b__10_0(System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs @ 35]
00007F70B20FCB80 00007f7129af88c2 System.Threading.Thread.StartCallback() [/_/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @ 105]
00007F70B20FCCF0 00007f71a37ab9c7 [DebuggerU2MCatchHandlerFrame: 00007f70b20fccf0] 
...

从卦中数据看有很多的 Unknown,说明dump取得不好,可能不是用正规的 dotnet-dump 或者 procdump,但不管怎么说,还是可以看到大量的和 Kafka 有关的链接库,并且从 InnerInvoke 这个执行 m_action 来看,应该是有大量线程卡在 Kafka 中的某个函数上。

有了这些知识,最后给到朋友的建议如下:

  • 修改 DOTNET_DefaultStackSize 参数

可以仿照 windows 上的 .netcore 默认 1.5M 的栈空间设置,因为8M真的太大了,扛不住,也和 Linux 的低内存使用不符。

  • 观察 Kafka 的相关逻辑

毕竟有大量线程在 Kafka 的等待上,个人觉得可能是订阅线程太多,或者什么业务执行时间长导致的线程饥饿,尽量把线程压下去。

三:总结

Linux 上的 .NET 调试生态在日渐丰富,这是一件让人很兴奋的事情,最后再给 WinDbg 点个赞,它不仅可以全平台dump分析,还可以实时调试 Linux 进程,现如今的WinDbg真的是神一般的存在。
图片名称

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hjln.cn/news/44380.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

腾讯公益赛个人冲刺博客19(2024.6.7)

今天测试成功,可以实现ai聊天

腾讯公益赛个人冲刺博客20(2024.6.10)

今天总结第二阶段成果,对于新加的录像功能和帮扶功能的接收板块进行系统综合测试。

Android自动化-如何获取视图元素属性?

在做Android自动化时候,我们需要知道视图有哪些元素,元素都有哪些属性,获取到属性我们才能获取到元素从而做自动化控制,所以做Android自动化获取元素属性是必要的第一步 获取视图元素属性最便捷的方式就是使用Android SDK中的 uiautomatorviewer,当你配置好Android的开发环…

SAP: SALV GRID 追加按钮

利用类追加刷新按钮创建一个利用控制器的SALV程序。SAP: ABAP SALV GRID 追加按钮 , 利用类追加刷新按钮创建一个利用控制器的SALV程序。 异常的描述: 运行时错误: UNCAUGHT_EXCEPTION 优质生活从拆开始

[转帖]Linux 最新SO_REUSEPORT特性

https://oms.inspur.com/cwbase/web/gsprtf/main.aspx 1、前言昨天总结了一下Linux下网络编程“惊群”现象,给出Nginx处理惊群的方法,使用互斥锁。为例发挥多核的优势,目前常见的网络编程模型就是多进程或多线程,根据accpet的位置,分为如下场景:(1)单进程或线程创建soc…

Android自动化无障碍服务开源库-Assists v3.0.0

Assists v3.0.0 Android无障碍服务(AccessibilityService)开发框架,快速开发复杂自动化任务、远程协助、监听等Android无障碍服务能做什么 利用Android无障碍服务可以开发一些Android系统内的自动化任务,比如经典的微信自动抢红包、支付宝蚂蚁森林自动浇水、芭芭农场自动施…

dotnet 如何访问到 UNO 框架里面的 internal 不公开成员

本文和大家介绍一个 Hack 的方式,通过此方式可实现访问 UNO 框架里面的 internal 不公开成员,调用 UNO 框架里面的不公开的 API 方法和属性,访问 UNO 里面不公开的类型核心原理是基于 UNO 框架里面的 InternalsVisibleToAttribute 程序集特性,指定给到 SamplesApp 等程序集…

codesandbox 使用记录

1 登录2 导入github项目3 配置虚拟机和系统环境 3.1 配置虚拟机配置3.2 根据项目开发语言版本配置系统环境模板最后点击apply启动项目,等待依赖安装完成,即可在csbox运行自己的项目啦