.net重点知识复习(内存管理、垃圾回收、反射)

托管执行过程(Managed Execution Process)

执行托管代码的过程包括下列步骤:

1.选择编译器。

为获得公共语言运行时提供的优点,必须使用一个或多个针对运行时的语言编译器,例如Visual Basic,C#,Visual C++,F#,或很多如Eiffel,Perl或COBOL等这样的第三方编译器。

2.将代码编译为 MSIL。

编译将源代码翻译为 Microsoft 中间语言 (MSIL) 并生成所需的元数据。

3.将 MSIL 编译为本机代码。

在执行时(At execution time),实时 (JIT) 编译器将 MSIL 翻译为本机代码。在此编译过程中,代码必须通过验证过程,该过程检查 MSIL 和元数据以查看是否可以将代码确定为类型安全。

4.运行代码。

公共语言运行时(CLR,the common language runtime)提供使执行能够发生以及可在执行期间使用的各种服务的基础结构。1

.net自动内存管理

自动内存管理也就是俗称的垃圾回收(garbage collection,GC),它是公共语言运行时在托管执行过程中提供的服务之一。 公共语言运行时的垃圾回收器为应用程序管理内存的分配和释放。 对开发人员而言,这就意味着在开发托管应用程序时不必编写执行内存管理任务的代码。 自动内存管理可解决常见问题,例如,忘记释放对象并导致内存泄漏,或尝试访问已释放对象的内存。

分配内存

在垃圾回收器由 CLR 初始化之后,它会分配一段内存用于存储和管理对象。此内存称为托管堆。每个托管进程都有一个托管堆。 进程中的所有线程都在同一个托管堆上为对象分配内存。

初始化新进程时,运行时会为进程保留一个连续的地址空间区域,也就是托管堆(the managed heap)。托管堆维护着一个指针,指向将在堆中分配的下一个对象的地址。托管堆上部署着所有引用类型。应用程序创建第一个引用类型时,将在托管堆的基址为该类型分配内存。应用程序创建下一个对象时,垃圾回收器(GC,the garbage collector)在紧接第一个对象后面的地址空间内为它分配内存。只要地址空间可用,垃圾回收器就会继续以这种方式为新对象分配空间。

从托管堆中分配内存要比非托管内存分配速度快。由于运行时通过递增指针值来为对象分配内存,所以这几乎和从堆栈(stack)中分配内存一样快。另外,由于连续分配的新对象在托管堆中是连续存储,所以应用程序可以快速访问这些对象。

释放内存

垃圾回收器的优化引擎根据所执行的分配决定执行回收的最佳时间,以释放应用程序不再使用的对象的内存。它通过检查应用程序的根对象来确定不再使用的对象。每个应用程序都有一组根对象。每个根或者引用托管堆中的对象,或者被设置为空。应用程序的根包含全局和静态对象指针,线程堆栈中的局部变量和引用对象参数,以及 CPU 寄存器。垃圾回收器可以访问由实时 (JIT) 编译器和运行时维护的活动中的根的列表,对照此列表检查应用程序的根,并在此过程中创建一个图表(graph),其中包含所有可从这些根中访问的对象。

不在该图表中的对象将无法从应用程序的根中访问。垃圾回收器会将无法访问的对象视为垃圾,并释放为它们分配的内存。在回收中,垃圾回收器检查托管堆,查找无法访问对象所占据的地址空间块。发现无法访问的对象时,它就使用内存复制功能来压缩内存中可以访问的对象,释放分配给不可访问对象的地址空间块。在压缩了可访问对象的内存后,垃圾回收器就会做出必要的指针更正,以便应用程序的根指向新地址中的对象。它还将托管堆指针定位至最后一个可访问对象之后。请注意,只有在回收时发现大量无法访问的对象时,才会压缩内存。如果托管堆中的所有对象均未被回收,则不需要压缩内存。

为了改进性能,运行时会将大型对象分配到一个单独的堆里。垃圾回收器会自动释放大型对象的内存。但是,为了避免移动内存中的大型对象,不会压缩此内存。

代(Generations)和性能

GC使用“标记并压缩”(Mark and Compact)算法分析程序运行对象的关系,将不可达部分对象从内存中清理掉。GC会从应用程序的根对象开始,通过对对象树形结构遍历来判定一个对象是否可达,而并不是向COM那样跟踪每个对象的引用。GC在其专门的线程中运行,GC不但自动为程序清理不再使用的内存,还会在每次运行时压缩托管堆。因此内存中空余的空间会是一片连续的内存。

为优化垃圾回收器的性能,将托管堆分为三代:第 0 代、第 1 代和第 2 代。运行时的垃圾回收算法基于以下几个普遍原理,这些垃圾回收方案的原理已在计算机软件业通过实验得到了证实。首先,压缩托管堆的一部分内存要比压缩整个托管堆速度快。其次,较新的对象生存期较短,而较旧的对象生存期则较长。最后,较新的对象趋向于相互关联,并且大致同时由应用程序访问。

运行时的垃圾回收器将新对象存储在第 0 代中。在应用程序生存期的早期创建的对象如果未被回收,则被升级并存储在第 1 代和第 2 代中。 本主题中稍后介绍了对象升级过程。因为压缩托管堆的一部分要比压缩整个托管堆速度快,所以此方案允许垃圾回收器在每次执行回收时释放特定级别的内存,而不是整个托管堆的内存。

实际上,垃圾回收器在第 0 代托管堆已满时执行回收。如果应用程序在第 0 代托管堆已满时尝试新建对象,垃圾回收器将会发现第 0 代托管堆中没有可分配给该对象的剩余地址空间,这时垃圾回收器从检查第 0 代托管堆中的对象(而不是托管堆中的所有对象)开始执行回收,以尝试为对象释放第 0 代托管堆中的地址空间。这是最有效的途径,因为新对象的生存期往往较短,并且期望在执行回收时,应用程序不再使用第 0 代托管堆中的许多对象。另外,单独回收第 0 代托管堆通常可以回收足够的内存,这样,应用程序便可以继续创建新对象。

垃圾回收器执行第 0 代托管堆的回收后,会压缩可访问对象的内存,如本主题前面的释放内存中所述。然后,垃圾回收器升级这些对象,将托管堆的这一部分视为第 1 代。因为未被回收的对象往往具有较长的生存期,所以将它们升级至更高的级别很有意义。因此,垃圾回收器在每次执行第 0 代托管堆的回收时,不必重新检查第 1 代和第 2 代托管堆中的对象。

在执行第 0 代托管堆的首次回收并把可访问的对象升级至第 1 代托管堆后,垃圾回收器将考虑第 0 代托管堆的其余部分。它将继续为第 0 代托管堆中的新对象分配内存,直至第 0 级托管堆已满并需执行另一回收为止。这时,垃圾回收器的优化引擎会决定是否需要检查较旧的级别中的对象。例如,如果第 0 级托管堆的回收没有回收足够的内存,不能使应用程序成功完成创建新对象的尝试,垃圾回收器就会先执行第 1 代托管堆的回收,然后再执行第 2 代托管堆的回收。如果这样仍不能回收足够的内存,垃圾回收器将执行第 2、1 和 0 代托管堆的回收。每次回收后,垃圾回收器都会压缩第 0 代托管堆中的可访问对象并将它们升级至第 1 代托管堆。第 1代级托管堆中未被回收的对象将会升级至第 2 代托管堆。由于垃圾回收器只支持三个级别,因此第 2 代托管堆中未被回收的对象会继续保留在第 2 代托管堆中,直到在将来的回收中确定它们为无法访问为止。

事实上,Java 和 C# 程序员就算不懂 GC 的内部作法,影响并不大。程序员只要注意下面两件事,即可在大多数的情况下让 GC 发挥效率:

· 不用的指针要及早设定为null,以在下一次的 GC 中被清除。

· 许多程序员制造了大量的短命对象,仍然浑然未觉。例如:Java和C#的「字符串加法」,C#语法允许自动地box(也就是自动地把value type转成reference type)。

为非托管资源释放内存

对于应用程序创建的大多数对象,可以依赖垃圾回收器自动执行必要的内存管理任务。但是,非托管资源需要显式清除。最常用的非托管资源类型是包装(wrap)操作系统资源的对象,例如,文件句柄、窗口句柄、网络连接或数据库连接。虽然垃圾回收器可以跟踪封装非托管资源的托管对象的生存期,但却无法具体了解如何清理资源。创建封装非托管资源的对象时,建议在公共 Dispose 方法中提供必要的代码以清理非托管资源。通过提供 Dispose 方法,对象的使用者可以在使用完对象后显式释放其内存。使用封装非托管资源的对象时,应该了解 Dispose 并在必要时调用它。有关清理非托管资源的更多信息和实现 Dispose 的设计模式示例,请参见 垃圾回收和清理非托管资源。2
类型使用者(程序员)可直接调用 IDisposable.Dispose 实现以释放非托管资源使用的内存。在正确实现 Dispose 方法时,安全句柄的 Finalize 方法或 Object.Finalize 方法的重写会在未调用 Dispose 方法的情况下辅助清理资源。

反射(Reflection)机制

在 .NET Framework 中,任何东西都继承自 System.Object,并且一个对象类型的引用通常是反射的一般起点。

反射是一个程序集发现及运行的过程,通过反射可以得到*.exe或*.dll等程序集内部的信息。使用反射可以看到一个程序集内部的接口、类、方法、字段、属性、特性等等信息。

通过 System.Reflection 命名空间中的类以及 System.Type,可以获取有关已加载的程序集和在其中定义的类型(如类、接口和值类型)的信息。也可以使用反射在运行时创建类型实例,以及调用和访问这些实例。

需要指出的一点是,对于大多数系统,在运行时操作类型和对象,较之在源代码中静态地进行同等操作,会导致性能降低。由于反射的动态特性,这是个必要的取舍,不过有很多技巧和最佳做法可以优化反射的性能。3

  1. https://msdn.microsoft.com/zh-cn/library/k5532s8a%28v=vs.110%29.aspx “托管执行过程” ↩
  2. https://msdn.microsoft.com/zh-cn/library/f144e03t.aspx “自动内存管理”
    https://onedrive.live.com/view.aspx?resid=17DF440127EF27A0!1461&cid=17df440127ef27a0&app=Word “DotNet的自动内存管理”
    http://www.cnblogs.com/IPrograming/archive/2012/10/22/Effective_CSharp_Unit2.html#mark “《Effective C#》读书笔记——了解.NET内存管理机制<.NET资源管理>” ↩
  3. https://msdn.microsoft.com/zh-cn/magazine/cc163408.aspx#S5 “CLR 完全介绍:反射之反思” ↩

你可能感兴趣的:(net,内存管理)