保持.NET应用程序内存健康的六个优秀实践

开发 后端
大型 .NET 应用程序中的内存问题是某种无声的杀手。有点像高血压。你可以长期吃垃圾食品而忽略它,直到有一天你面临严重的问题。对于 .NET 程序,该严重问题可能是高内存消耗、主要性能问题和彻底崩溃。

[[410670]]

本文转载自微信公众号「DotNET技术圈」,作者michael。转载本文请联系DotNET技术圈公众号。

大型 .NET 应用程序中的内存问题是某种无声的杀手。有点像高血压。你可以长期吃垃圾食品而忽略它,直到有一天你面临严重的问题。对于 .NET 程序,该严重问题可能是高内存消耗、主要性能问题和彻底崩溃。在这篇文章中,您将看到如何将我们的应用程序的血压保持在健康水平。

你怎么知道你的内存使用情况是否健康?你需要做什么来保持它的健康?这正是本文要讨论的内容。我们将介绍 6 种最佳做法,以保持内存健康并在出现问题时检测问题。您还将看到优化垃圾收集并使您的应用程序非常快速的最佳实践。

1. 应尽快收集对象

为了使您的程序快速运行,主要目标是尽快收集对象。要理解为什么它很重要,您需要了解 .NET 的分代垃圾收集器。当使用该new子句创建对象时,它们是在第 0 代的堆上创建的。那是内存中非常小的空间。如果在有 Gen 0 集合时它们仍然被引用,它们将被提升到 Gen 1。Gen 1 是更大的内存空间。如果它们在有第 1 代集合时仍被引用,则将它们提升到第 2 代。

Gen 0 集合是最频繁的并且非常快。Gen 1 集合涵盖 Gen 0 内存空间和 Gen 1 内存空间,并且它们更昂贵。Gen 2 集合包括整个内存空间,包括大对象堆 (LOH)。它们非常昂贵。GC 已优化为具有许多 Gen 0 集合、较少的 Gen 1 集合和很少的 Gen 2 集合。但是,如果您有许多对象被提升到更高代,那么您将产生相反的效果。这会导致内存压力[1](又名 GC 压力)和性能不佳。

顺便说一下,新对象的分配非常便宜。您唯一需要担心的是集合。

那么如何在低代收集对象呢?很简单,只需确保不会尽快引用它们即可。有些对象,比如单例,必须永远在内存中。没关系,它们通常是不会消耗大量内存的服务。

2. 使用缓存……但要小心

根据定义,像缓存这样的机制很麻烦。这些是长期存在的临时对象,可能会升级到第 2 代。虽然这对 GC 压力不利,但通常值得付出代价,因为缓存确实可以帮助提高性能。但你必须密切关注它。

缓解部分内存压力的一种方法是使用可变缓存对象。这意味着不是替换缓存对象,而是更新现有对象。这意味着 GC 提升对象和启动更多 Gen 0 和 Gen 1 收集的工作更少。

这是一个例子。假设您正在缓存来自在线杂货店的库存商品。您有一个缓存机制来存储经常查询的项目的价格和数据。就像那些会导致高血压的冷冻比萨饼。假设每 5 分钟您必须使缓存无效并重新查询数据库,以防细节发生变化。因此,在这种情况下,不是创建新Pizza对象,而是更改现有对象的状态。

3. 留意 GC 中的时间百分比

如果您想知道垃圾收集对执行时间的影响有多大,这很容易做到。简单看看性能计数器.NET CLR Memory | % GC 时间。这将显示垃圾收集器使用了百分之多少的执行时间。有许多工具可以查看性能计数器。在 Windows 中,您可以使用 PerfMon。在 Linux 中,您可以使用dotnet-trace[2]。要了解更多信息,请查看我的文章Use Performance Counters in .NET to measure Memory, CPU, and Everything[3]。

我将给您一些神奇的数字,但请注意这些数字,因为一切都有其自身的背景。对于大型应用程序,10% 的 GC 时间可能是一个健康的百分比。GC 中 20% 的时间处于临界状态,任何更多都意味着您有问题。

4. 留意那些 Gen 2 Collections

除了 GC 中的时间百分比,您应该监控的另一个重要指标是 Gen 2 收集的数量。或者更确切地说是第 2 代收藏的速度。目标是尽可能少地使用它们。考虑到这些是完整的内存堆集合。当 GC 收集所有内容时,它们有效地冻结了应用程序的所有线程。

对于您应该拥有多少 Gen 2 系列,我无法给出一个神奇的数字。但我建议每隔一段时间积极监控这个数字,如果比率上升,那么你可能会添加一些非常糟糕的行为。您可以通过性能计数器.NET CLR Memory |查看该数字。% Gen 2 集合

PerfMon 显示第 2 代集合

5. 监控稳定的内存消耗

考虑应用程序的常规状态。有些事情一直在发生。它可能是一个服务请求的服务器,一个从队列中提取消息的服务,一个有很多屏幕的桌面应用程序。在此期间,您的应用程序不断创建新对象,执行一些操作,然后释放这些对象并返回到正常状态。这意味着从长远来看,内存消耗应该或多或少相同。当然,它可能会在高峰时间或繁重操作期间达到高水平,但一旦完成它应该会恢复正常。

但是,如果您监控了许多应用程序,您可能知道有时内存会随着时间的推移而增加。内存的平均消费缓慢上升到更高的水平,即使它在逻辑上不应该。这种行为的原因几乎总是内存泄漏[4]。这是一个对象不再使用的现象,但由于某种原因,它仍然被引用,因此从未被收集。

当一个操作导致对象泄漏时,每个这样的操作都会消耗更多的内存。随着时间的推移,记忆力上升。当足够的时间过去时,内存接近其极限。在 32 位进程中,该限制为 4GB。在 64 位进程中,它取决于机器约束。当我们如此接近极限时,垃圾收集器就会恐慌。它开始为每个其他分配触发全内存第 2 代收集,以免内存不足。这很容易使您的应用程序变慢。当更多的时间过去时,内存确实达到了它的极限,并且应用程序会因灾难性的OutOfMemoryException. 你有它 - 相当于心脏病发作。

为了确保您不会达到这种状态,我的建议是随着时间的推移主动监控内存消耗。最好的方法是查看性能计数器Process | Private Bytes。您可以使用Process explorer[5]或 PerfMon轻松完成。

6. 定期查找内存泄漏

内存问题的#1 罪魁祸首毫无疑问是内存泄漏。很容易造成它们,它们可以被长期忽视,最终会造成大量损害。在应用程序持续崩溃的阶段修复内存泄漏非常困难。您必须更改可能导致各种回归错误的旧代码。因此,我将为具有健康内存的应用程序添加第二个主要目标:修复并避免内存泄漏。

期望您的团队永远不会引入内存泄漏是不现实的。并且在每次新提交时检查整个应用程序中的内存泄漏是不切实际的。相反,我建议添加每隔一段时间检查内存泄漏的做法,它可能是每周、每月或每季度,具体取决于你的需求。

一种方法是在每次看到内存上升时检查内存泄漏(如提示 #5 中建议的那样)。但问题是内存占用低的泄漏也会导致很多问题。例如,您可能有一些本应收集但仍处于活动状态的对象,并且仍有代码在其中执行,这会导致不正确的行为。

检测和修复内存泄漏的最佳方法是使用内存分析器。在我的文章Demystifying Memory Profilers in C# .NET Part 2: Memory Leaks 中[6]了解如何做到这一点。

要了解哪种设计会导致内存泄漏,请查看我的文章.NET 中可能导致内存泄漏的 8 种方式[7]。

总结

所以你有它,一个健康记忆状态的秘诀。如果您遵循这些建议,您的应用程序将很快并且消耗很少的内存。但说真的,请吃健康的食物和锻炼??

References

[1] 内存压力: https://michaelscodingspot.com/avoid-gc-pressure/

[2] dotnet-trace: https://github.com/dotnet/diagnostics/blob/master/documentation/dotnet-trace-instructions.md

[3] Use Performance Counters in .NET to measure Memory, CPU, and Everything: https://michaelscodingspot.com/performance-counters/

[4] 内存泄漏: https://michaelscodingspot.com/ways-to-cause-memory-leaks-in-dotnet/

[5] Process explorer: https://docs.microsoft.com/en-us/sysinternals/downloads/process-explorer

[6] Demystifying Memory Profilers in C# .NET Part 2: Memory Leaks 中: https://michaelscodingspot.com/memory-profilers-for-memory-leaks/ 

[7] .NET 中可能导致内存泄漏的 8 种方式: https://michaelscodingspot.com/ways-to-cause-memory-leaks-in-dotnet/

 

责任编辑:武晓燕 来源: DotNET技术圈
相关推荐

2022-10-31 14:07:08

存储数据丢失

2021-07-19 10:06:30

数据治理数字化转型CIO

2020-07-24 00:41:18

物联网项目物联网IOT

2023-07-18 15:11:01

2021-08-07 09:32:23

数据治理数字化转型CIO

2020-03-16 08:00:00

物联网项目物联网IOT

2023-10-27 12:11:33

2013-03-11 10:37:08

2021-07-27 09:00:00

开发Web软件

2023-12-05 08:00:00

云原生

2020-03-24 14:45:17

程序员技能开发者

2022-04-06 21:29:44

边缘计算数据存储数据中心

2022-04-29 14:54:10

物联网应用程序设备

2024-02-20 14:48:40

2023-03-13 16:25:28

2015-08-24 15:25:13

云应用程序云服务SaaS

2011-08-29 10:03:38

开发团队

2022-09-12 16:02:32

Docker安全Node.js

2024-02-20 13:08:00

2023-06-12 17:59:48

点赞
收藏

51CTO技术栈公众号