转载请联系DotNET技术圈公众号。大型.NET应用程序中的内存问题是一种无声杀手。有点像高血压。你可以长期吃垃圾食品而忽略它,直到有一天你遇到严重的问题。对于.NET程序,严重的问题可能是高内存消耗、主要性能问题和彻底崩溃。在这篇文章中,您将了解如何使用我们的应用程序将您的血压保持在健康水平。你怎么知道你的内存使用是否健康?你需要做什么来保持它的健康?这正是本文的主题。我们将介绍6个最佳实践,以保持内存健康并在问题出现时检测到问题。您还将看到优化垃圾收集和使您的应用程序非常快的最佳实践。1.对象应该尽快收集为了你的程序能够快速运行,主要目标是尽快收集对象。要了解它为何重要,您需要了解.NET的分代垃圾收集器。当使用new子句创建对象时,它们是在第0代的堆上创建的。这是内存中非常小的空间。如果在有Gen0collection的时候还在引用它们,那么它们会被提升到Gen1。Gen1是更大的内存空间。如果在第1代收集时仍然引用它们,则将它们提升到第2代。第0代收集最频繁且非常快。Gen1集合涵盖了Gen0内存空间和Gen1内存空间,并且它们更昂贵。Gen2集合包括整个内存空间,包括大对象堆(LOH)。它们非常昂贵。GC被优化为具有许多Gen0收集、少量Gen1收集和少量Gen2收集。然而,如果你有很多对象被提升到更高的一代,你会产生相反的效果。这会导致内存压力[1](也称为GC压力)和性能不佳。顺便说一句,新对象的分配非常便宜。您唯一需要担心的是收藏。那么如何收集低代中的对象呢?很简单,只要确保它们不被尽快引用即可。有些对象,如单例,必须始终在内存中。没关系,它们通常是不会消耗大量内存的服务。2.使用缓存...但要小心像缓存这样的机制从定义上来说是很麻烦的。这些是可以提升到第2代的长期存在的临时对象。虽然这对GC压力不利,但通常值得付出代价,因为缓存确实可以提高性能。但你必须留意它。减轻内存压力的一种方法是使用可变缓存对象。这意味着不是替换缓存的对象,而是更新现有对象。这意味着GC提升对象和启动更多Gen0和Gen1收集的工作更少。这是一个例子。假设您正在缓存在线杂货店的库存商品。您有一个缓存机制来存储经常查询的项目的价格和数据。就像那些导致高血压的冷冻比萨饼。假设每5分钟您必须使缓存无效并重新查询数据库以防详细信息发生变化。因此,在这种情况下,不是创建新的Pizza对象,而是更改现有对象的状态。3.密切关注GC中的时间百分比如果您想知道垃圾回收对执行时间的影响有多大,这很容易做到。快速查看性能计数器.NETCLRMemory|%GC时间。这将显示垃圾收集器使用了多少百分比的执行时间。有许多工具可以查看性能计数器。在Windows中,您可以使用PerfMon。在Linux中,您可以使用dotnet-trace[2]。要了解更多信息,请查看我的文章在.NET中使用性能计数器来测量内存、CPU和一切[3]。我会给你一些神奇的数字,但要小心这些数字,因为每件事都有它自己的背景。对于大型应用程序,10%的GC时间可能是一个健康的百分比。GC中20%的时间是关键的,任何更多的时间都意味着你有问题。4.密切关注那些第2代回收除了GC中的时间百分比之外,您应该监控的另一个重要指标是第2代回收的数量。或者更确切地说是Gen2收集的速度。目标是尽可能少地使用它们。考虑到这些是内存堆的完整集合。当GC收集所有内容时,它们实际上冻结了应用程序的所有线程。我无法确定您应该拥有多少个Gen2系列。但我建议每隔一段时间主动监控这个数字,如果这个数字上升,那么你可能会添加一些非常糟糕的行为。您可以通过PerformanceCounters.NETCLRMemory|查看该数字%Gen2collectionsPerfMon显示Gen2collections5.监控稳定的内存消耗,同时考虑到应用程序的一般状态。有些事情一直在发生。它可以是处理请求的服务器、从队列中提取消息的服务、具有多个屏幕的桌面应用程序。在此期间,您的应用程序不断创建新对象,执行一些操作,然后释放这些对象并返回到正常状态。这意味着从长远来看,内存消耗应该大致相同。当然,它可能会在高峰时段或繁重的操作期间达到高水平,但一旦完成,它应该会恢复正常。但是,如果您监控许多应用程序,您可能知道有时内存会随着时间的推移而增加。内存的平均消耗缓慢上升到更高的水平,即使它在逻辑上不应该。这种行为的原因几乎总是内存泄漏[4]。这是一种不再使用对象的现象,但由于某种原因它仍然被引用,因此从未被收集。当一个操作导致对象泄漏时,每个这样的操作都会消耗更多的内存。记忆力会随着时间的推移而提高。当足够的时间过去时,内存接近其极限。在32位进程中,限制为4GB。在64位进程中,它取决于机器限制。当我们接近极限时,垃圾收集器会恐慌。它开始为所有其他分配触发全内存Gen2收集,这样它就不会耗尽内存。这很容易减慢您的应用程序。当更多的时间过去时,内存确实达到了它的限制,并且应用程序崩溃并出现灾难性的OutOfMemoryException。你有它-相当于心脏病发作。为确保您不会达到这种状态,我的建议是随着时间的推移主动监控内存消耗。最好的方法是查看性能计数器Process|私有字节。您可以使用Processexplorer[5]或PerfMon轻松完成。6.定期查找内存泄漏内存问题的头号罪魁祸首无疑是内存泄漏。它们很容易引起,它们可能会长时间不被注意,并最终造成很大的伤害。在应用程序不断崩溃的阶段修复内存泄漏非常困难。您必须更改可能导致各种回归错误的旧代码。因此,我会为具有健康内存的应用程序添加第二个主要目标:修复和避免内存泄漏。期望您的团队永远不会引入内存泄漏是不现实的。而且在每次新提交时检查整个应用程序的内存泄漏是不切实际的。相反,我建议添加经常检查内存泄漏的做法,可以是每周一次、每月一次或每季度一次,具体取决于您的需要。一种方法是在每次看到内存增加时检查内存泄漏(如提示#5中所建议的)。但问题是低内存占用的泄漏也会导致很多问题。例如,您可能有一些应该被收集但仍然存在的对象,并且仍在其中执行代码,这会导致不正确的行为。检测和修复内存泄漏的最佳方法是使用内存分析器。在我的文章DemystifyingMemoryProfilersinC#.NETPart2:MemoryLeaks[6]中了解如何执行此操作。要了解哪种设计会导致内存泄漏,请查看我的文章8WaysYouCanCauseMemoryLeaksin.NET[7]。总结所以你已经知道了,保持健康记忆状态的秘诀。如果您遵循这些建议,您的应用程序将很快并且消耗很少的内存。但说真的,请吃健康的食物和运动??参考资料[1]内存压力:https://michaelscodingspot.com/avoid-gc-pressure/[2]dotnet-trace:https://github.com/dotnet/diagnostics/blob/master/documentation/dotnet-trace-instructions.md[3]在.NET中使用性能计数器来测量内存、CPU和一切:https://michaelscodingspot.com/performance-counters/[4]内存泄漏:https://michaelscodingspot.com/ways-to-cause-memory-leaks-in-dotnet/[5]进程资源管理器:https://docs.microsoft.com/en-us/sysinternals/downloads/process-explorer[6]揭秘C#.NET中的内存分析器第2部分:内存泄漏:https://michaelscodingspot.com/memory-profilers-for-memory-leaks/[7]8种.NET可能导致内存泄漏的方式:https://michaelscodingspot.com/ways-to-cause-内存泄漏-in-dotnet/
