1. 首页
  2. 后端

.NET8内存泄漏分析方法 – 托管内存

  .NET8内存泄漏分析方法 - 托管内存

====================

前几天给一个企业系统做了内存泄漏分析,总结了一些方法,分享给大家。

这篇主要介绍托管内存的泄漏分析方法,程序基于 .NET8,部署在Linux环境。

大家可以从这里下载演示代码:github.com/dotnet/samp…

1、安装内存跟踪和转储工具

这里需要两个工具:

  • dotnet-counters 粗略的跟踪内存使用情况,判断是否发生了内存泄漏。
  • dotnet-dump 记录并分析详细的内存使用情况,找到内存的泄漏点。
dotnet tool install --global dotnet-counters

dotnet tool install --global dotnet-dump

2、运行程序

进入到程序目录,执行下边的命令,启动程序:

dotnet run

3、找到进程Id

新起一个终端,执行下边的命令查找所有的 dotnet 进程。

dotnet-counters ps

这个命令会列出所有的dotnet进程,我们根据名字找到需要跟踪的进程。

.NET8内存泄漏分析方法 - 托管内存

4、跟踪内存使用情况

执行下边的命令持续跟踪进程内存的使用情况。

dotnet-counters monitor --refresh-interval 1 -p 40546

--refresh-interval 指定两次刷新之间的秒数:

.NET8内存泄漏分析方法 - 托管内存

重点关注上图中的 GC Heap Size(MB),这是托管内存堆,所有的托管内存在这里管理。初始情况下大概4M。

5、触发内存泄漏

在浏览器中打开:https://localhost:5001/api/diagscenario/memleak/20000

这里摘抄下部分关键代码:

 private static Processor p = new Processor();

 [HttpGet]
        [Route("memleak/{kb}")]
        public ActionResult<string> memleak(int kb)
        {
            int it = (kb * 1000) / 100;
            for (int i = 0; i < it; i++)
            {
                p.ProcessTransaction(new Customer(Guid.NewGuid().ToString()));
            }

            return "success:memleak";
        }

memleak/20000 这个请求会向一个静态集合中添加2000个Customer实例。

在.NET中静态变量会被垃圾回收器看作root对象,不被回收,如果我们一直向其中添加内容而不移除,就会导致内存泄漏。

此时我们可以看到 GC Heap Size(MB)的大小有了明显的增长,请求 memleak/20000 多次,观察一段时间,内存只增不减。这就可以粗略的判断存在内存泄漏的可能性。

6、转储程序内存

我们使用dotnet-dump工具把当前的内存使用情况记录下来:

dotnet-dump collect -p 40546

它会生成一个文件(文件可能比较大,请注意硬盘空间变化,以免耗尽空间),如下图所示:

.NET8内存泄漏分析方法 - 托管内存

7、分析转储文件

继续使用 dotnet-dump 分析内存使用情况:

dotnet-dump analyze core_20240712_231917

(1)先来看看托管堆的整体状态,找到内存使用比较多的对象。

dumpheap -stat

.NET8内存泄漏分析方法 - 托管内存

这里我们可以看到 String 和 Customer 的实例比较多,它们的泄漏可疑性非常大。

(2)查看具体类型的实例在内存中的地址列表,以 String 为例。

执行下边的命令(其中00010842d7a8是上图中String类型的地址):

dumpheap -mt 00010842d7a8

.NET8内存泄漏分析方法 - 托管内存

列表可能很长,我们尽量跟踪那些比较有规律的数据,比如Size大小都差不多的数据,复制他们的Address出来。

(3)查看对象的根和引用列表

gcroot 命令可以让我们看到谁在引用某个具体的内存实例。gcroot后边是一个内存地址,从上图返回的列表中复制而来。

.NET8内存泄漏分析方法 - 托管内存

可以看到,String 实例正是被 Customer 的实例持有,一直可以定位到 Processor 的实例,正是我们在 Controller 中定义的 private static Processor p = new Processor();

但看这一个地址还不能确定问题,我们还要看看更多的String实例引用关系,如果大多数 String 对象都遵循类似的引用方式,就基本可以确定问题发生在这个上下文中。

此时我们再去看代码,分析其中的逻辑,如果确实会导致内存一直增加而不释放,那就找到了真正的泄露点。然后我们就可以修改相关的代码逻辑,及时释放不再需要的数据。

回到这个代码例子中,我们也可能真的需要这样一个静态变量来缓存一些数据,此时我们应该确保它缓存的数据大小不会超出机器可分配的内存大小,否则程序可能就会崩溃。

原文链接: https://juejin.cn/post/7393185003184390182

文章收集整理于网络,请勿商用,仅供个人学习使用,如有侵权,请联系作者删除,如若转载,请注明出处:http://www.cxyroad.com/16997.html

QR code