.net - .NET 内存泄漏 - 增加峰值,然后回到基线
问题描述
我对 Azure-app-service-webjob 的生产中的内存泄漏感到头疼。
这是一个后台工作进程,按计划从队列中读取工作。大约每 10 分钟,内存使用量就会达到峰值,然后(大约 10 分钟后)会恢复到正常基线。每个尖峰每次都略高。直到最终,峰值达到足够高(> 80% 说),因此需要越来越长的时间才能返回基线,最后锁定。
我已经非常详细地登录了。没有大量或大量的数据库查询,并且没有任何处理操作花费超过几秒钟的时间。很难获得非常清晰的跟踪,因为在 10 分钟的周期内可能会发生 15-30 次不同的操作。
反正。不久前,当它处于“最大化”阶段时,我得到了一个完整的内存转储,将其插入 WinDbg。尽管数据库中只有几千个,但内存中有数百万个某种类型的实体框架 (6) 实体。
我无法在本地复制。所以我在这个实体类型的构造函数中添加了一些代码——它保留了从构造函数中看到Dictionary<string,long>
某个特定的次数。Environment.StackTrace
我正在等待“最大化”发生,但是远程连接,目前看起来很标准/正常。
鉴于这些对象/可能/随着时间的推移而增加,但这并不能解释增加的峰值并返回基线。可以?
我还刚刚在“基线”期间捕获了完整的内存转储,然后是“小峰值”(根据图像仅运行了几个小时)。我有基本的 WinDbg 技能。
无论如何,我的问题/混淆的原因:
- 如何确定两个完整内存转储之间的差异?
- 有没有人见过这样的事情?
- 什么可能导致内存中的峰值每次都增长?
- 如果它是内存泄漏,为什么它会在峰值之间返回到基线?
我想认为没有魔法发生,但我根本找不到与尖峰相吻合的东西:
- 数据库记录的数量逐渐增加,但只有几千条,如果进程重新启动,内存问题会重新设置
- 尽管峰值持续约 10 分钟,但根据日志记录,似乎没有任何操作需要超过几秒钟
解决方案
如何确定两个完整内存转储之间的差异?
在 WinDbg 中很难做到。如果您注意使用正确的格式,使用Jetbrains dotMemory等内存分析工具会容易得多,它可以导入原始转储。
有没有人见过这样的事情?
是的。
什么可能导致内存中的峰值每次都增长?尽管数据库中只有几千个,但内存中有数百万个某种类型的实体框架 (6) 实体。
如果你有一个 O(n²) 循环,比如
foreach(...)
foreach(...)
CreateAnObject();
那么数据库中的 1000 行可能会创建 1.000.000 个对象。如果您只是在数据库中再添加一行,那么下次您运行相同的查询时就会多出 2001 个对象。
如果它是内存泄漏,为什么它会在峰值之间返回到基线?
我不会将尖峰行为称为内存泄漏。看起来还不错。但是,您需要考虑到在某个时间点,RAM 不再足够,并且会发生交换到硬盘的情况。然后,您的应用程序会变得慢得多。也许你可以改变算法。
但是,请注意基线不是恒定的:
所以你确实有内存泄漏,但它与尖峰无关。我不会比较尖峰和非尖峰,而是比较两个基线。
数据库记录的数量逐渐增加,但只有几千条,如果进程重新启动,内存问题会重新设置
如果基线泄漏得到修复,这可能会得到解决。
尽管峰值持续约 10 分钟,但根据日志记录,似乎没有任何操作需要超过几秒钟
没有手术?你有什么手术?方法调用?您如何确保测量所有方法调用?下一次,您可能还想添加一个 CPU% 图表。
推荐阅读
- python - 在windows上部署flask应用
- r - 从 R 中的网络对象中获取每个路径
- android - org.mockito.exceptions.misusing.NotAMockException:参数应该是一个模拟,但是是:类 java.lang.Class for Andriod SSLContext.getInstance() 方法
- nginx - Flask + Gunicorn + Nginx:未安装组 www-data
- mysql - 使用带有值的聚合:分组:django
- php - 使用 PHP 读取 MySQL 数据库
- c++ - 如何递归地基于子小部件使 GTK focus_out 事件触发或如何检查是否没有子控件具有焦点
- javascript - 使用 Gulp-Notify 在终端中显示项目文件夹路径,而不是硬盘上项目的整个路径
- javascript - 如何使用带有异步获取的for循环返回数组
- node.js - 当我们在 Node.js 中使用 http.createServer()