win7 iis网站设置,手机能看的好网站,很久以前做相册mv的网站,网页设计工作怎么样原文来自互联网#xff0c;由长沙DotNET技术社区编译。本文来源#xff1a;https://michaelscodingspot.com/find-fix-and-avoid-memory-leaks-in-c-net-8-best-practices/从事大型企业项目的任何人都知道内存泄漏就像是大型酒店中的老鼠。当它们很少时#xff0c;您可能不会… 原文来自互联网由长沙DotNET技术社区编译。本文来源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内存分析器中的大人物 是dotMemorySciTech内存分析器 和 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。随时使用StreamsGraphics或Files 对于 例如您可能正在执行非托管代码。使用非托管代码的.NET框架类通常实现IDisposable。那是因为非托管资源需要明确地释放这发生在Dispose方法中。您唯一的工作就是记住并调用Dispose方法。如果可能请使用using语句。public void Foo()
{using (var stream new FileStream(C:\Temp\SomeFile.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 MBprivate 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 MDMicrosoft.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之类的内存分析器提供了一个测试APIMemAssertion.NoInstances(typeof(MyLeakyClass));
MemAssertion.NoNewInstances(typeof(MyLeakyClass), lastSnapshot);
MemAssertion.MaxNewInstances(typeof(Bitmap), 10);
摘要你的新年决心是怎样的?我新年的决心是更好的内存管理。我希望这篇文章能给您带来一些价值如果您订阅[9]我的博客或在下面发表评论我将非常乐意。欢迎任何反馈。References[1] 难以利用: https://knowledge.ni.com/KnowledgeArticleDetails?idkA00Z0000019S9cSAElen-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/