本文来源:https://michaelscodingspot.com/find-fix-and-avoid-memory-leaks-in-c-net-8-best-practices/

从事大型企业项目的任何人都知道内存泄漏就像是大型酒店中的老鼠。当它们很少时,您可能不会注意到,但是您必须始终保持警惕,以防它们过多,闯入厨房并制造混乱。

查找,修复和学习避免内存泄漏是一项重要技能。

我将列出我和高级.NET开发人员为我提供建议的8种最佳实践技术,这些技术将教您检测应用程序中何时存在内存泄漏问题,查找特定的内存泄漏并进行修复。最后,我将介绍监视和报告已部署程序的内存泄漏的策略。

定义.NET中的内存泄漏

在垃圾收集环境中,术语“内存泄漏”有点违反直觉。当有垃圾收集器(GC)负责收集所有内容时,我的内存为何还会泄漏?

有两个相关的核心原因。第一个核心原因是当您具有仍被引用但实际上未使用的对象时。由于已引用它们,垃圾收集器将不会收集它们,并且它们将永久保留,占用内存。例如,当您注册事件但从不注销时,可能会发生这种情况。

第二个原因是当您以某种方式分配非托管内存(没有垃圾回收)并且不释放它时。这并不难做到。.NET本身有很多分配非托管内存的类。几乎所有涉及流,图形,文件系统或网络调用的操作都是在后台进行的。通常,这些类实现** Dispose **方法,该方法释放内存(稍后再讨论)。您可以使用特殊的.NET类(如Marshal)或PInvoke(有一个进一步的示例)轻松地自己分配非托管内存。

让我们进入我的最佳实践技术列表:

1.使用诊断工具窗口检测内存泄漏问题

如果您去调试 | Windows | 显示诊断工具,您将看到此窗口。如果您像我一样,则可能在安装Visual Studio之后看到了此工具窗口,立即关闭了它,再也没有想到它。诊断工具窗口可能会非常有用。它可以轻松地帮助您检测两个问题:内存泄漏和GC压力。

当您有内存泄漏时,“进程内存”图如下所示:

图片

从顶部的黄线可以看到GC正在尝试释放内存,但它仍在不断上升。

当您具有GC Pressure时,过程内存图如下所示:

图片

“ GC压力”是在创建新对象并将它们处置得太快而导致垃圾收集器无法跟上时。如图所示,内存已接近极限,GC突发非常频繁。

您将无法通过这种方式找到特定的内存泄漏,但是您可以检测到内存泄漏问题,这本身就很有用。在Enterprise Visual Studio中,“诊断”窗口还包括一个内置的内存探查器,该探查器确实可以查找特定的泄漏。我们将在最佳实践#3中讨论内存分析。

2.使用任务管理器,Process Explorer或PerfMon检测内存泄漏问题

检测主要内存泄漏问题的第二种最简单方法是使用任务管理器或Process Explorer(来自SysInternals)。这些工具可以显示您的进程使用的内存量。如果它随着时间不断增加,则可能是内存泄漏。

图片

性能监视器是有点难以利用[1],但能证明你的内存使用量随时间的一个很好的曲线图。这是我的应用程序的图形,它不停地分配内存而不释放它。我正在使用过程 | 专用字节计数器。

图片

请注意,此方法众所周知是不可靠的。您可能只是因为GC尚未收集内存而增加了内存使用量。还有共享内存和私有内存的问题,因此您可能会错过内存泄漏和/或诊断不是您自己的内存泄漏(说明[2])。最后,您可能将内存泄漏误认为是GC Pressure。在这种情况下,您不会发生内存泄漏,但是创建和处理对象的速度如此之快,以至于GC无法跟上进度。

尽管有缺点,但我还是提到了这种技术,因为它既易于使用,有时又是唯一的工具。这也是一个不错的指标,长时间观察时出了点问题。

3.使用内存分析器检测内存泄漏

内存分析器就像处理内存泄漏的厨师刀。它是查找和修复它们的主要工具。尽管其他技术可能更易于使用或更便宜(探查器许可证价格昂贵),但最好精通至少一个内存探查器以有效解决内存泄漏问题。

.NET内存分析器中的大人物 是:dotMemory,SciTech内存分析器 和 ANTS Memory Profiler。如果您拥有Visual Studio Enterprise,则还有一个“免费”分析器。

所有内存分析器都以类似的方式工作。您可以附加到正在运行的进程,也可以打开转储文件。探查器将为您的进程的当前内存堆创建一个快照。您可以通过各种方式分析快照,例如,以下是当前快照中所有已分配对象的列表:

图片

您可以看到每种类型分配了多少实例,它们占用了多少内存以及GC Root的引用路径。

GC根是GC无法释放的对象,因此GC根引用所引用的所有内容也无法释放。当前活动线程的静态对象和本地对象是GC根。在了解.NET中的垃圾收集中了解更多信息。

最快,最有用的性能分析技术是比较内存应返回相同状态的2个快照。在操作之前拍摄第一个快照,在操作之后拍摄另一个快照。确切的步骤是:

1.从应用程序中的某种空闲状态开始。这可能是主菜单或类似的东西。2.通过附加到进程或保存转储,使用Memory Profiler拍摄快照。3.运行怀疑会导致内存泄漏的操作。返回到空闲状态。4.拍摄第二张快照。5.将这两个快照与您的内存分析器进行比较。6.研究新创建的实例,它们很可能是内存泄漏。检查“ GC根目录的路径”,并尝试了解为什么未释放这些对象。

这是一个很棒的视频,其中在SciTech内存分析器 中比较了2个快照,并发现了内存泄漏:

4.使用“ Make Object ID”查找内存泄漏

在上一篇文章5避免C#.NET中的事件造成内存泄漏的技术中,您应该知道[3] 我展示了一种通过在类Finalizer中放置断点来查找内存泄漏的技术。在这里,我将向您展示一种类似的方法,该方法更易于使用,并且不需要更改代码。这利用了调试器的Make Object ID功能和Instant Window。

假设您怀疑某个类存在内存泄漏。换句话说,您怀疑在运行特定方案后,此类仍保持引用状态,并且GC从未收集过此类。要确定GC是否真正收集了它,请按照下列步骤操作:

1.在创建类实例的地方放置一个断点。2.将鼠标悬停在变量上以打开调试器的数据提示,然后右键单击并使用Make Object ID。您可以在立即窗口$ 1中键入以查看是否正确创建了对象ID。3.完成本应从实例中释放实例的方案。4.使用已知的魔术线强制进行GC收集

GC.Collect();   GC.WaitForPendingFinalizers();   GC.Collect();

5. 原文中有两端视频,由于微信审核的缘故,无法上传,有兴趣的可以查看原文。

参考这个流程,您可以通过在立即窗口中键入魔术线来强制进行垃圾收集,从而使该技术成为完全的调试体验,而无需更改代码。

重要提示:这种做法在.NET Core 2.X调试器(问题[4])中不能很好地工作。强制在与对象分配相同的范围内进行垃圾回收不会释放该对象。通过将另一种方法中的垃圾回收强制超出范围,您可以花费更多的精力。

5.当心常见的内存泄漏源

始终存在导致内存泄漏的风险,但是某些模式更有可能造成内存泄漏。我建议在使用这些工具时要格外小心,并使用最新的最佳做法等技术来主动检查内存泄漏。

以下是一些较常见的违规者:

•.NET中的事件因导致内存泄漏而臭名昭著。您可以无辜地订阅一个事件,甚至在不怀疑的情况下导致破坏性的内存泄漏。这个主题是如此重要,以至于我专门写了整篇文章:您应该知道的5种避免C#.NET中的事件造成内存泄漏的技术[5]•特别是静态变量,集合和静态事件应该总是看起来可疑。请记住,所有静态变量都是GC根,因此GC绝不会收集它们。•缓存功能 –任何类型的缓存机制都可以轻易导致内存泄漏。通过最终将高速缓存信息存储在内存中,它将填满并导致OutOfMemory异常。解决方案可以是定期删除较早的缓存或限制缓存量。•WPF绑定可能很危险。经验法则是始终绑定到DependencyObject或一种 INotifyPropertyChanged 宾语。如果您这样做失败,WPF将从静态变量创建对绑定源(即ViewModel)的强引用,从而导致内存泄漏。此有用的StackOverflow线程中有关WPF绑定泄漏的更多信息•被捕获的成员 –可能很明显,事件处理程序方法意味着引用了一个对象,但是当变量在匿名方法中被捕获时,也会被引用。这是内存泄漏的示例:

public class MyClass {  private int _wiFiChangesCounter = 0;  public MyClass(WiFiManager wiFiManager)  {      wiFiManager.WiFiSignalChanged += (s, e) =>      _wiFiChangesCounter++;  } }

•永不终止的线程 – 每个线程的活动堆栈都被视为GC根。这意味着在线程终止之前,GC不会收集其在堆栈上的变量的任何引用。这也包括计时器。如果您的Timer的滴答处理程序是一个方法,则该方法的对象被视为已引用,并且不会被收集。这是内存泄漏的示例:

public class MyClass   {       public MyClass(WiFiManager wiFiManager)     {        Timer timer = new Timer(HandleTick);   timer.Change(TimeSpan.FromSeconds(5),  TimeSpan.FromSeconds(5));    }    private void HandleTick(object state)      {         // do something      }  }

有关此主题的更多信息,请查看我的文章8 .NET中导致内存泄漏的方法[6]。

6.使用“处置”模式来防止非托管内存泄漏

您的.NET应用程序不断使用非托管资源。

.NET框架本身在很大程度上依赖于非托管代码来进行内部操作,优化和Win32 API。

随时使用Streams,Graphics或Files 对于 例如,您可能正在执行非托管代码。使用非托管代码的.NET框架类通常实现IDisposable。那是因为非托管资源需要明确地释放,这发生在Dispose方法中。您唯一的工作就是记住并调用Dispose方法。如果可能,请使用using语句。

public void Foo(){    using (var stream = new FileStream(@"C:TempSomeFile.txt",                                       FileMode.OpenOrCreate))    {        // do stuff     }// stream.Dispose() will be called even if an exception occurs}

在使用语句转换的代码放到一个尝试/最后的场景,在后面声明的Dispose方法被调用的最后方法。但是,即使您不调用Dispose方法,这些资源也将被释放,因为.NET类使用Dispose Pattern[7]。这基本上意味着,如果之前未调用Dispose,则在对象被垃圾回收时从Finalizer调用它。也就是说,如果您没有内存泄漏并且确实调用了终结器。

当您自己分配非托管资源时,则绝对应该使用Dispose模式。这是一个例子:

public class MyClass : IDisposable{    private IntPtr _bufferPtr;    public int BUFFER_SIZE = 1024 * 1024; // 1 MB    private bool _disposed = false;     public MyClass()    {        _bufferPtr =  Marshal.AllocHGlobal(BUFFER_SIZE);    }     protected virtual void Dispose(bool disposing)    {        if (_disposed)            return;         if (disposing)        {            // Free any other managed objects here.        }         // Free any unmanaged objects here.        Marshal.FreeHGlobal(_bufferPtr);        _disposed = true;    }     public void Dispose()    {        Dispose(true);        GC.SuppressFinalize(this);    }     ~MyClass()    {        Dispose(false);    }}

这种模式的重点是允许显式处置资源。还要增加一种保护措施,如果未调用Dispose(),则将在垃圾回收期间(在Finalizer中)处置您的资源。

该GC.SuppressFinalize(这)也很重要。如果该对象已经存在,则可以确保终结器未在垃圾回收上被调用处置。使用终结器的对象将以不同的方式释放,并且成本更高。将终结器添加到称为F-Reachable-Queue的对象中,这使该对象在额外的GC生成后仍然存在。还有其他并发症[8]。

7.从代码添加内存遥测

有时,您可能想定期记录您的内存使用情况。也许您怀疑生产服务器存在内存泄漏。当您的内存达到一定限制时,您可能想采取一些措施。或者,也许您只是养成监视内存的好习惯。

我们可以从应用程序本身中获取很多信息。使用当前的内存很简单:

Process currentProc = Process.GetCurrentProcess(); var bytesInUse = currentProc.PrivateMemorySize64; 

有关更多信息,可以使用用于PerfMon的PerformanceCounter类:

PerformanceCounter ctr1 = new PerformanceCounter("Process", "Private Bytes", Process.GetCurrentProcess().ProcessName); PerformanceCounter ctr2 = new PerformanceCounter(".NET CLR Memory", "# Gen 0 Collections", Process.GetCurrentProcess().ProcessName);  PerformanceCounter ctr3 = new PerformanceCounter(".NET CLR Memory", "# Gen 1 Collections", Process.GetCurrentProcess().ProcessName);  PerformanceCounter ctr4 = new PerformanceCounter(".NET CLR Memory", "# Gen 2 Collections", Process.GetCurrentProcess().ProcessName);  PerformanceCounter ctr5 = new PerformanceCounter(".NET CLR Memory", "Gen 0 heap size", Process.GetCurrentProcess().ProcessName);  //...  Debug.WriteLine("ctr1 = " + ctr1 .NextValue());  Debug.WriteLine("ctr2 = " + ctr2 .NextValue());  Debug.WriteLine("ctr3 = " + ctr3 .NextValue());  Debug.WriteLine("ctr4 = " + ctr4 .NextValue());  Debug.WriteLine("ctr5 = " + ctr5 .NextValue());  

可从任何perfMon计数器获得信息,这是很多信息。但是,您可以更深入。CLR MD(Microsoft.Diagnostics.Runtime)允许您检查当前的内存堆并获取任何可能的信息。例如,您可以打印内存中所有已分配的类型,包括实例计数,根目录路径等。您几乎从代码中获得了一个内存探查器。

要了解使用CLR MD可以实现的目标,请查看Dudi Keleti的DumpMiner。

所有这些信息都可以记录到文件中,甚至更好地记录到遥测工具(如Application Insights)中。

8.测试内存泄漏

主动测试内存泄漏是一个好习惯。这并不难。您可以使用以下简短模式:

[Test]  void MemoryLeakTest()  {    var weakRef = new WeakReference(leakyObject)    // Ryn an operation with leakyObject      GC.Collect();      GC.WaitForPendingFinalizers();    GC.Collect();    Assert.IsFalse(weakRef.IsAlive); }

为了进行更深入的测试,诸如SciTech的.NET Memory Profiler和dotMemory之类的内存分析器提供了一个测试API:

MemAssertion.NoInstances(typeof(MyLeakyClass));    MemAssertion.NoNewInstances(typeof(MyLeakyClass), lastSnapshot);   MemAssertion.MaxNewInstances(typeof(Bitmap), 10);  

摘要

你的新年决心是怎样的?我新年的决心是:更好的内存管理。

我希望这篇文章能给您带来一些价值,如果您订阅[9]我的博客或在下面发表评论,我将非常乐意。欢迎任何反馈。

References

[1] 难以利用: https://knowledge.ni.com/KnowledgeArticleDetails?id=kA00Z0000019S9cSAE&l=en-IL[2] 说明: https://stackoverflow.com/a/1986486/1229063[3] 5避免C#.NET中的事件造成内存泄漏的技术中,您应该知道: https://michaelscodingspot.com/2018/12/14/5-techniques-to-avoid-memory-leaks-by-events-in-c-net-you-should-know/[4] 问题: https://github.com/dotnet/coreclr/issues/20156[5] 您应该知道的5种避免C#.NET中的事件造成内存泄漏的技术: https://michaelscodingspot.com/2018/12/14/5-techniques-to-avoid-memory-leaks-by-events-in-c-net-you-should-know/[6] 8 .NET中导致内存泄漏的方法: https://michaelscodingspot.com/ways-to-cause-memory-leaks-in-dotnet/[7] Dispose Pattern: https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose[8] 其他并发症: https://www.jetbrains.com/help/dotmemory/Analyzing_GC_Roots.html[9] 订阅: https://michaelscodingspot.com/subscribe/

wpf绑定treeview 带查找_如何查找,修复和避免C#.NET中内存泄漏的8个最佳实践相关推荐

  1. 如何查找,修复和避免C#.NET中内存泄漏的8个最佳实践

    原文来自互联网,由长沙DotNET技术社区编译. 本文来源:https://michaelscodingspot.com/find-fix-and-avoid-memory-leaks-in-c-ne ...

  2. java数组线性查找_数组查找: 线性查找与二分查找

    前言 从数组中查找你需要的数据,是一个很常见的需求,那么当你查找所需数据时,用什么方法查找速度最快? 本文将通过图文形式,详细讲解线性查找与二分查找,并用JavaScript将其实现,欢迎各位感兴趣的 ...

  3. python 二分查找_二分查找算法总结

    二分查找的思想是通过每次折半快速找到一个数,例如,我们经常玩的游戏猜数字,在0~1000,随便出一个数字98让对方猜,首先猜500,对方给提示比500大还是小,如果数字小于500,就继续猜250,依次 ...

  4. c 给定字符串中查找_面试 | 查找类算法精析

    点击上方蓝字设为星标 每周一.三.五上午 8:30 准时推送 下面开始今天的学习- 前言 查找,是使用计算机处理问题时的一个最基本的任务,因此也是算法面试中非常常见的一类问题.很多算法问题的本质,就是 ...

  5. xtragrid 某个值 查找_二分查找(下):如何快速定位IP对应的省份地址?

    通过IP地址来查找IP归属地的功能,不知道你有没有用过?没用过也没关系,你现在可以打开百度,在搜索框里随便输一个IP地址,就会看到它的归属地. 这个功能并不复杂,它是通过维护一个很大的IP地址库来实现 ...

  6. sql server键查找_如何查找SQL Server版本

    sql server键查找 In this article, we will explore how to find the SQL Server version details with vario ...

  7. 前端里的button怎么去除点击自带边框_自媒体人做视频时配音中的噪音如何用EQ和采样去除(详细介绍)...

    为了给今日头条奉献优质的声音和视频作品,大家几乎在所有的视频制作中都会用到解说声音或者现场的收录的声音,也都想获得较为理想的效果,那今天我就给大家详细解读如何才能获得更好的声音效果,并且重点会给大家讲 ...

  8. 网络研讨室_即将举行的网络研讨会:调试生产中Java的5种最佳实践

    网络研讨室 您的团队是否花费超过10%的时间在生产中调试Java? 将新代码部署到生产中是一项艰巨的任务. 在您的本地环境中起作用的东西在生产中的作用并不相同,您可以通过用户来了解. 不理想吧? 生产 ...

  9. antimalware service executable占用内存_解决 vue 项目运行过程中内存泄漏问题

    vue-cli3.0 内存溢出 JavaScript heap out of memory vue-cli3.0构建的项目,开发过程中,可能会遇到内存溢出的情况,改动一点代码,代码编译,进程就会断掉, ...

最新文章

  1. 英伟达对ARM、Linux开放光线追踪,SDK已就位,网友:switch也能跑光追的节奏?...
  2. dd命令测试linux磁盘io情况,【LINUX】正确的使用dd进行磁盘读写速度测试
  3. Effective C# 原则35:选择重写函数而不是使用事件句柄(译)
  4. P3193 [HNOI2008]GT考试
  5. ANT:fileset中使用exclude
  6. 【英语学习】【WOTD】regale 释义/词源/示例
  7. (转)编码剖析@Resource注解的实现原理
  8. [转] js中的钩子机制(hook)
  9. S71200PLC程序博图V14 西门子博图编写
  10. 微信小程序图片上传组件
  11. 地图切图 java_多任务切图 | SuperMap iDesktop Java
  12. 请求头显示Provisional headers are shown问题
  13. 秦小明 第六讲 投融资,资产运作
  14. 万物互联之边缘计算简述-背景
  15. Windows自带远程连接Ubuntu桌面
  16. 携程,京东,4399静态页面总结
  17. 让你的BT迅雷下载速度提升十倍以上....
  18. 2022中科院自动化所人工智能暑期学校(部分内容)
  19. 三维建模分享之蒸汽坦克
  20. JDBC规范——(3)新特性

热门文章

  1. 一个比较简单驱动程序初学者可以看看
  2. mysql键太长_数据库,主键为何不宜太长长长长长长长长?(转)
  3. mac升级php后旧版本还在,Mac下更新自带的PHP版本
  4. DW里面html鼠标点击特效,dw制作鼠标经过时图像放大鼠标离开图像回原形效果
  5. ug添加imachining变量_UG用的不够快?是不是还没建标准库
  6. SQLite | Insert、Delete、Updata 与 Drop 语句
  7. Cell子刊主编:文章被编辑拒稿,主要是这4大原因
  8. 这个为生信学习打造的开源Bash教程真香!!(目录更新)!
  9. 大名鼎鼎的电影胶片滤镜--DxO FilmPack 5 mac
  10. 程序猿的数学:scratch篇