dotnet应用程序的预期内存大小与Linux下真实的消耗内存之间的不匹配

f2uvfpb9  于 2023-11-17  发布在  Linux
关注(0)|答案(2)|浏览(169)

如果能帮助我理解内存转储的大小,我将不胜感激。背景:我有一个net 7控制台应用程序(主应用程序)在容器中运行,它与另一个net 7控制台应用程序(sidecar)一起托管在Kubernetes中。在测试场景下,主应用程序的内存是~570MB,我得到了一个完整的内存转储(使用procdump for Linux或dotnet dump).基于目标环境,我需要减少容器的内存,并开始分析内存中的内容,以了解是什么消耗了这么多。
所有GC堆的合并大小:94 MB模块大小:69,3 MB本机堆大小:51.8 MB嵌入式资源(例如位图):0线程数:49(总数),20(活动)
我的粗略计算是:GCHeap(94)+ 2* Modules(69,3)+ Native Heap(51,8)+1 MB * Threads(49)= 333,4MB(我将Modules的大小加倍,作为jitted assembly的近似值)
-->我预期的内存使用量和实际消耗的内存之间的差异是~ 238MB (42%)
我错过了什么?有人能告诉我任何文档/工具/等,以了解意外的内存是从哪里来的?
我试着在dotMemory和dotnet dump中分析转储,但卡住了,因为我发现的数字看起来不错,但总内存消耗太高。
编辑(1):来自dotMemory Heap Fragmentation的屏幕截图:
x1c 0d1x的数据

yiytaume

yiytaume1#

一些内存可能被托管堆保留,但由于堆碎片而未使用。值“GCHeap 94MB”包括完整堆大小还是仅包括分配对象的大小?请在dotMemory中打开此转储并附上Inspections | Heap fragmentation部分的屏幕截图?

zwghvu4y

zwghvu4y2#

我试着回答我自己的问题,并列出我的学习:
(1)核心转储还包含dotnet使用的本机库,这些库可以分配自己的内存页面(自己的堆)。对于我的应用程序,加载了以下库:
| 名称|大小[KiB]|
| --|--|
| /usr/lib/x86_64-linux-gnu/libicudata.so.67.1| 27748 |
| /usr/lib/x86_64-linux-gnu/libicui18n.so.67.1| 3092 |
| /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1| 3008 |
| /usr/lib/x86_64-linux-gnu/libicuuc.so.67.1| 1948 |
| /lib/x86_64-linux-gnu/libc-2.31.so| 1856 |
| /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28| 1832 |
| /lib/x86_64-linux-gnu/libm-2.31.so| 1296 |
| /usr/lib/x86_64-linux-gnu/libssl.so.1.1| 588 |
| /lib/x86_64-linux-gnu/ld-2.31.so| 172 |
| /lib/x86_64-linux-gnu/libpthread-2.31.so| 120 |
| /lib/x86_64-linux-gnu/libgcc_s.so.1| 104 |
| /lib/x86_64-linux-gnu/libresolv-2.31.so| 96 |
| /lib/x86_64-linux-gnu/libnss_files-2.31.so| 56 |
| /lib/x86_64-linux-gnu/librt-2.31.so| 40 |
| /lib/x86_64-linux-gnu/libnss_dns-2.31.so| 32 |
| /lib/x86_64-linux-gnu/libdl-2.31.so| 24 |
(2)在Debian Linux下,默认的线程堆栈大小取决于操作系统,它是8 MiB
(3)被“分配”的内存页面将被存储在核心转储中(只要它们没有被标记为Not Include in Core Dump(dd),这与它们是否被加载到物理内存中无关。
(4)内存页面分配的信息,并不存储在核心转储本身。它可以通过cat /proc/<pid>/mapscat /proc/<pid>/smaps(smap包含更多细节)检索。请阅读proc man page了解详细信息。
(5)内存页面,基于一个名为memfd:doublemapper (deleted)的文件,基本上是jit'ed代码。它被称为双Map器,因为同一个内存页面被dotnetMap两次,一次是可写的,第二次是可执行的,以避免安全问题。
如果memfd:doublemapper (deleted)内存页面在其他时间增长,并且应用程序在运行时不使用System.Emit生成代码,则可能是委托或匿名函数无法被dotnet运行时缓存,并且必须一次又一次地jit这些代码。
(6)如果smap显示匿名内存页面,占用了意外的内存,那么需要使用原生Linux工具来跟踪这些分配。
作为第一步,重要的是加载调试符号:

  1. echo "install dotnet-symbol tool"
  2. dotnet tool install dotnet-symbol --tool-path /tmp/tools
  3. echo "use dotnet-symbol to get debug symbols of current dotnet version"
  4. /tmp/tools/dotnet-symbol /usr/share/dotnet/shared/Microsoft.NETCore.App/7.0.??/*
  5. echo "Add Package Server containign debug symbol packages"
  6. echo "deb http://ddebs.ubuntu.com $(lsb_release -cs) main restricted universe multiverse
  7. deb http://ddebs.ubuntu.com $(lsb_release -cs)-updates main restricted universe multiverse | \
  8. deb http://ddebs.ubuntu.com $(lsb_release -cs)-proposed main restricted universe multiverse" | \
  9. tee -a /etc/apt/sources.list.d/ddebs.list
  10. echo "Install debug symbol packages"
  11. apt install -y --no-install-recommends libc6-dbg libssl3-dbgsym openssl-dbgsym libicu67-dbgsym libstdc++6-dbgsym

字符串
下一步是选择正确的调试器,我会从heaptrack开始,如果这还不足以使用strace

  1. echo "Run demo application under heaptrack"
  2. heaptrack -o /mnt/diagnostics/heaptrack.$(echo $HOSTNAME)_$(date +%T).zst /usr/bin/dotnet /app/demo.dll
  3. echo "Run demp application under strace, limit to memory releated system calls"
  4. strace -k -f -e trace=mmap,munmap,mprotect -o /mnt/diagnostics/$(echo $HOSTNAME)_memory_trace_$(date +%T).txt /usr/bin/dotnet /app/demo.dll


heaptrack输出的分析可以通过它的gui应用程序或使用命令行工具heaptrack_print -l 1 -a 0 -T 0 -p 0 -f /mnt/diagnostics/heaptrack.MyHostName_12:00:00.zst来完成。

展开查看全部

相关问题