c#

位置:IT落伍者 >> c# >> 浏览文章

使用.NET Profiler API检查并优化程序的内存使用


发布日期:2021年08月25日
 
使用.NET Profiler API检查并优化程序的内存使用

运行在 Windows 下面的程序分配内存以便表现所需要的不同类型的资源可以将这些分配当作用来封装程序所需要的内存和其他任何资源状态的对象

应用程序正确运行时系统将释放被使用的资源和内存以便让系统中的其他程序使用但有时候如果应用程序出现错误则资源状态或内存(或者这二者)都不会被正确释放这就会造成资源或内存洩漏这些错误可能是很难识别的垃圾回收器 (GC) 负责确保程序所分配的用于完成任务的内存能够在不需要开发人员关注它的情况下被释放

对垃圾回收了解得越多就越能更好地构造程序与之配合使用NET 中的对象是从称为托管堆的一片内存中分配来的堆被描述为托管是因为您向它申请内存后垃圾回收器会负责执行清理工作这似乎需要很多开销因为垃圾回收器必须跟蹤在 NET 公共语言运行库 (CLR) 中所分配的每个对象但实际上它工作得很有效率

对象可以是小型对象可以包含少量整数或更大的数据也可以包含数据库连接和很多状态信息对象可以是独立的也可以在内部包含或使用其他对象GC 的工作是确定什么时候应当回收对象以便释放内存供其他程序使用当它认为它已被装满时就会对可以删除的对象作标记然后从托管堆中将它们删除当垃圾回收器试图分配新的对象却发现托管堆没有更多的可用内存时垃圾回收器就会认为它已被装满GC 试图分配内存但确定它已被装满时它将尝试清理已为您的应用程序分配的某些内存以便为新对象腾出空间

GC 以略微不同的方式看待您的对象并在决定什么时候回收它认为不再有用的对象时考虑到这些对象的差异它这样做的一个方法是它有一组根对象用来确定哪些对象可以被回收如果对对象的引用大体上属于如下分类中的某一个则该引用就被看作是根全局或静态对象指针线程的堆栈上的所有局部变量和参数对象指针或包含托管堆中的对象的指针的任何 CPU 寄存器如果对象的引用是根引用那么它可能有或可能没有与它关联的还会在垃圾回收后幸存的子对象GC 首先找到根对象然后沿着引用找到被根引用的其他对象以便避免回收这些对象

如图 所示托管堆中有四个被分配的对象(S)mall(L)arge(F)inalized 和 (R)eferenced假设每个对象通过其主要特征(例如小型对象都不会包含引用或其他组合)来标识自己在堆中分配这些对象时它们将相互紧邻地放在内存中我也有一个位于 (G)lobal 范围的根引用它包含对 Z 的引用

GC 开始垃圾回收时它首先假设所有对象都是不必要的直到这些对象被证明是需要的为止对象基本上通过它认识谁或引用了谁或谁引用了它或认识它来证明自己是必要的对于 GC根引用为谁认识谁提供了起点GC 从根对象开始沿着对象层次结构检查引用情况以确定对象是否是可到达的或是否有可能被另一个对象使用如果对象被证明是可到达的则它不是该垃圾回收周期的处理对象如果对象被证明无法从任何引用到达它则 GC 将把该对象标记为可回收然后它会被丢弃GC 使用标记和压缩方法这意味着一旦 GC 确定对象是垃圾则 GC 的另一个部分将删除无法到达的对象并将压缩堆中的空间以确保分配将继续非常快速地进行

GC 以代的方式看待回收周期中所涉及的对象每当对象被认为是可到达的时它就会被提升到下一代这意味着引用您的对象的对象越多或您的对象的操作范围越大它的存活时间就越长GC 当前最多有三代 代通常填充较小短期使用的对象并且回收它们的次数最多这意味着如果您有小型或很少使用的对象则它们将被频繁地回收 代和第 代是寿命更长和被更频繁访问的对象的储存库因此被回收的频率更低GC 中一个基本假设是您的程序中有更小寿命更短的对象更频繁地清理它们对您有好处理解这一点很重要因为您设计系统的方式会对您使用多少内存和占用内存多长时间有巨大的影响这是由于您的工作集将是大型的工作集内存使用量越大应用程序性能将降低得越多

字节以下的对象被认为是小型对象并且从托管堆的主要部分直接分配超过 字节的对象从托管堆的特殊部分(称为大型对象堆)分配托管堆对待小型和大型对象的方式有两个主要差异首先小型对象在被压缩时将移到托管堆内而大型对象则不是这样其次大型对象总是被当作第 代的一部分而小型对象通常被当作第 代的一部分如果您分配了很多短寿命的大型对象这将造成第 代被更频繁地回收由于从第 代到第 代越往后的回收成本越高这将有损应用程序的性能

我想讨论的垃圾回收的最后一个方面是终结 (finalization) 的概念当对象被 GC 回收时终结帮助开发人员释放他们在其对象中使用的资源对象需要实现 Finalize 方法才能完成该操作当对象要被销毁时GC 将调用 Finalize 方法以便允许对象清理它的内部资源和状态在 C# 和托管 C++ 中Finalize 方法实际上伪装在析构函数的语法 (~Object) 中这里的 Finalize 方法与纯 C++ 中的 Finalize 方法之间的重大差异是在 C# 和托管 C++ 中只有当 GC 清理对象时才调用该方法而在纯 C++ 的析构函数中当对象脱离范围时才会调用该方法将 Finalize 方法添加到您的对象中意味着它将总是被 GC 调用但要小心因为将 Finalize 方法添加到对象中时该对象将总是会在对第一代的垃圾回收后幸存下来因此所有终结对象的寿命会更长由于试图让 GC 尽可能有效地执行清理因此只有当您有非托管资源需要清理或者在对象创建成本高昂的特殊情况下(对象池)才应当使用终结

让我们返回图 中的原始示例该示例有一个托管堆其中包含四个对象和一个根引用如果在这个时候发生垃圾回收(这是由于这时不满足启动垃圾回收的条件而开发人员手动干预造成的)结果是 (S)mall 对象将被当作垃圾回收

大型对象将在该垃圾回收后幸存下来因为大型对象被指派为第 被终结的对象被 GC 注意到并且将调用 Finalize 方法但是对象本身仍将保留下来直到进行下一次垃圾回收为止(在某些情形下可能会更长)包含根引用 G 的对象将保留下来因为它是根引用是可到达的

现在让我们假设下一次发生的垃圾回收针对的是第 到第 代(可以通过调用 SystemGCCollect 方法并将 作为参数来完成该操作)(L)arge 对象将在第 代清理期间被回收而 (F)inalized 对象在第 代回收期间被回收这是因为 Finalize 已被调用并且已在回收开始之前结束操作只有包含全局引用的对象仍然存在因而会在应用程序生存期内保留下来

良好的内存使用率

GC 负责处理内存洩漏但它不能防止内存保留作为开发人员您可以控制您的对象的生存期如果可以减少应用程序的工作集则性能将有所提高如果您的应用程序被设计为有很多对象长时间存活则可能会有内存洩漏即使最后清理了内存仍然会有损性能所以知道您的对象存活多长时间是值得的

GC 可以提供很大帮助但它只能处理我讨论过的一种原始类型的洩漏资源洩漏仍然是个问题但如果将非托管资源包装在终结类中GC 仍然可以帮助您确保正确处置它们最好对对象实现 Close 或 Dispose 方法以便在使用完对象时资源可以尽可能早得到清理而不用等待 GC 来清理它们(在您停止使用对象后等待 GC 清理它们可能需要很长时间)如果您对使用非托管资源的类实现了 Finalize并且正在使用托管堆则可以相当安全地避免真正的洩漏当然这并不意味着您应当让应用程序的工作集很庞大因为这仍然会有损性能

Profiler API 概述

为了说明应用程序使用了多少内存以及对象存在了多久我开发了一个称为 MemoryUsage 的应用程序MemoryUsage 有两个不同的部分第一部分编写为 C# 应用程序它将启动要监视的进程并在目标进程中设置一个环境变量以指示 CLR 应当加载 NET 分析器 (profiler)第二部分编写为基于 C++ 的 NET 分析器该分析器名为 MemProfilerCLR 将通过环境变量中的信息加载它NET 分析器是使用作为 CLR 的一部分提供的 Profiler API 来编写的它允许分析器作为被监视的进程的一部分运行并在发生某些事件时接收通知当应用程序执行时它为您提供各种通知为了从 CLR 接收这些通知您要提供一个 Profiler API 中指定的回调接口 (ICorProfilerCallback)然后当各种事件发生时CLR 将调用这个回调接口的方法(参见图

下面是需要注意的主要分析器回调方法RuntimeSuspendStartedRuntimeSuspendFinishedRuntimeResumeStartedObjectAllocatedObjectsAllocatedByClassMovedReferencesRootReferences 和 ObjectReferences

如果不熟悉 Profiler API可以阅读 Profilerdoc(位于 Visual Studio NET 安装目录下面的 \FrameworkSDK\Tool Developers Guide\docs 文件夹中)来了解某些更深入的信息

使用分析器时有几件事情要考虑到包括线程安全和同步以及分析器对性能的影响Profiler API 实际上允许您将它作为 CLR 的一部分运行这样因为多个线程将调用您的分析器所以您必须知道存在同步问题Microsoft 提供的 Profiler API 规范声明回调不会被序列化这就需要由开发人员自己来正确保护他的代码方法是创建线程安全的数据结构并在一旦需要防止多个线程并行访问代码时锁定分析器代码

我需要使对对象跟蹤系统以及在我

上一篇:Visual Basic.Net引人注目的语言革新

下一篇:实战Visual Basic.Net对话框