前言

虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC),但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC)。另外,了解内存管理可以帮助我们理解在每一个程序中定义的每一个变量是怎样工作的。

简介

这一节我们将介绍垃圾回收机制GC以及一些提搞程序性能的技巧。

绘图Graphing

让我们站在GC的角度研究一下。如果我们负责“扔垃圾”,我们需要制定一个有效的“扔垃圾”计划。显然,我们需要判断哪些是垃圾,哪些不是。

为了决定哪些需要保留,我们假设任何没有正在被使用的东西都是垃圾(如角落里堆积的破旧纸张,阁楼里一箱箱没有用的过时产品,柜子里不用的衣服)。想像一下我们跟两个好朋友生活在一起:JIT 和CLR。JIT和CLR不断的跟踪他们正在使用的东西,并给我们一个他们需要保留的东西列表。这个初始列表我们叫它“根(root)”列表。因为我们用它做起点。我们将保持一个主列表去绘制一张图,图中分布着所有我们在房子中需要保留东西。任何与主列表中有关联的东西也被画入图中。如,我们保留电视就不要扔掉电视遥控器,所以电视遥控器也会被画入图中。我们保留电脑就不能扔掉显示器键盘鼠标,同样也把它们画入图中。

这就是GC怎么决定去保留对象的。GC会保留从JIT和CLR那收到的一个根(root)对象引用列表,然后递归搜索对象引用并决定什么需要保留。

这个根的构成如下:

  • 全局/静态 指针。通过以静态变量的方式保持对象的引用,来确保对象不会被GC回收。
  • 栈里的指针。为了程序的执行,我们不想扔掉那些程序线程始终需要的对象。
  • CPU寄存器指针。托管堆里任何被CPU内存地址指向的对象都需要被保留。

在上面的图中,托管堆中的对象1,5被根Roots引用,3被1引用。对象1,5是被直接引用,3是通过递归查询找到。如果关联到我们之前的假设,对象1是我们的电视,对象3则是电视遥控器。当所有对象画完后,我们开始进行下一阶段:垃圾清理。

GC垃圾清理Compacting

现在我们有了一张需要保留对象的关系图,接下来进行GC的清理。

图中对象2和4被认定为垃圾将被清理。清理对象2,复制( memcpy )对象3到2的位置。

由于对象3的地址变了,GC需要修复指针(红色箭头)。然后清理对象4,复制( memcpy )对象5到原来3的位置(译外话:GC原则:堆中对象之间是没有间隙的,以后会有文章专门介绍GC原理)。

最后清理完毕,新对象将被放到对象5的上面(译外话:GC对一直管理一个指针指向新对象将被放置的地址,如黄色箭头,以后会有文章专门介绍)。

了解GC原理可以帮助我们理解GC清理(复制 memcpy ,指针修复等)是怎么消耗掉很多资源的。很明显,减少托管堆里对象的移动(复制 memcpy )可以提高GC清理的效率。

托管堆之外的终止化队列Finalization Queue和终止化-可达队列Freachable Queue

有些情况下,GC需要执行特定代码去清理非托管资源,如文件操作,数据库连接,网络连接等。一种可行性方案是使用析构函数(终结器):

class Sample
  1. {

  2. ~Sample()

  3. {

  4. // FINALIZER: CLEAN UP HERE 终结器:在这里清理

  5. }

  6. }

译外话:析构函数会被内部转换成终结器override Finializer()

有终结器的对象在创建时,同时在Finalization Queue里创建指向它们的指针(更正原文说的把对象放到Finalization Queue里):

上图对象1,4,5实现了终结器,因此在Finalization Queue里创建指向它们的指针。让我们看一下,当对象2和4没有被程序引用要被GC清理时会发生什么情况。

对象2会被以常规模式清理掉(见文章开始部分)。GC发现对象4有终结器,则会把Finalization Queue里指向它的指针移到Freachable Queue中,如下图:

但是对象4并不被清理掉。有一个专门处理Freachable Queue的线程,当它处理完对象4在Freachable Queue里的指针后,会把它移除。

这时对象4可以被清理了。当下次GC清理时会把它移除掉。换句话说, 至少执行 两次GC清理才能把对象4清理掉,显然会影响程序性能。

创建终结器,意味着创建了更多的工作给GC,也就会消耗更多资源影响程序性能。因此,当你使用终结器时一定要确保你确实需要使用它。

更好的方法是使用 IDisposable接口。

public class ResourceUser : IDisposable
  1. {

  2. #region IDisposable Members

  3. public void Dispose()

  4. {

  5. // 在这里清理!!!

  6. }

  7. #endregion

  8. }

实现 IDisposable接口的对象可以使用using关键字:

using (ResourceUser rec = new ResourceUser())
{

// 具体实现。。。

} // {}代码块结束时,会调用DISPOSE方法

变量rec的作用域是大括号内,大括号外不可访问。

静态变量

  1. class Olympics

  2. {

  3. public static Collection<Runner> TryoutRunners;

  4. }

  5. class Runner

  6. {

  7. private string _fileName;

  8. private FileStream _fStream;

  9. public void GetStats()

  10. {

  11. FileInfo fInfo = new FileInfo(_fileName);

  12. _fStream = _fileName.OpenRead();

  13. }

  14. }

如果你初始化了TryoutRunners,那么它将永远不会被GC清理,因为有静态指针一直指向初始化的对象。一旦调用了Runner里GetStats()方法,因为GetStats()里面没有文件关闭操作,它将永远被打开也不会被GC清理。我们可以看到程序的崩溃即将来临。

总结

一些良好的操作可以提高程序的性能:

  1. 清理。不要打开资源而不关闭它。关闭所有你打开的连接。尽可能快的清理所有非托管资源。一般规则:使用非托管对象,初始化越晚越好,清理越早越好。
  2. 不要过度引用。合理使用引用对象。如果某一个对象还存在没有被GC清理,所有它引用的对象都将不会被GC清理,如此递归下去。。。当我们完成使用一个引用对象时,把它设为NULL(视你的情况而定,注意不要产生空引用异常)。当引用少了,GC开始创建清理关系图graphing时过程就简单一些了,进而提高程序性能。
  3. 谨慎使用终结器Finalizaer或析构函数。能使用IDisposible代替就使用IDisposible。
  4. 保持对象及其成员的紧凑。如果声明一个对象并且它由多个子对象组成,尽可能的把它们放在一起初始化,好让它们所在的内存空间紧凑。GC复制这样的一大块内存比复制分散的内存碎片要容易。

译外话:

我会在以后的文章里更详细的介绍GC垃圾回收机制,包括GC划分的0代generation 0,1代generation 1,2代generation 2。有时只有一篇文章或一种图解还是会让人迷惑,所以下一篇介绍GC垃圾回收的内容更详细,图解也有不同。

翻译:http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory_401282006141834PM/csharp_memory_4.aspx

【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第六节 理解垃圾回收GC,提搞程序性能****相关推荐

  1. 【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第二节 栈基本工作原理

    栈基本工作原理 导航 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第 ...

  2. 【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈

    理解堆与栈 导航 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第二节 ...

  3. 【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第五节 引用类型复制问题及用克隆接口ICloneable修复

    前言 虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC),但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC).另外,了解内存管理可以帮助我们理解在每一个程序中 ...

  4. 【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第四节 参数传递对堆栈的影响 2

    前言 虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC),但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC).另外,了解内存管理可以帮助我们理解在每一个程序中 ...

  5. 【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第四节 参数传递对堆栈的影响 1

    前言 虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC),但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC).另外,了解内存管理可以帮助我们理解在每一个程序中 ...

  6. 【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第三节 栈与堆,值类型与引用类型

    前言 虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC),但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC).另外,了解内存管理可以帮助我们理解在每一个程序中 ...

  7. .net C# 堆 栈 垃圾回收 GC

    .NET C# .NET C# .NET C# .NET C# .NET C# .NET C# .NET C# 栈 堆 垃圾回收 GC #1 尽管在.NET framework下我们并不需要担心内存管 ...

  8. STM32:堆和栈(Heap Stack)及SRAM存储使用

    STM32堆和栈(Heap & Stack)及SRAM存储使用 编译一个程序,出现下面的信息: 明明程序没有什么内容,为什么变量大小就有RW+ZI=52+1836=1888字节大小了呢,就已经 ...

  9. stm32 堆和栈(stm32 Heap Stack)

    关于堆和栈已经是程序员的一个月经话题,大部分有是基于os层来聊的. 那么,在赤裸裸的单片机下的堆和栈是什么样的分布呢?以下是网摘: 刚接手STM32时,你只编写一个 int main() { whil ...

最新文章

  1. 保证你现在和未来不失业的10种关键技【转载】
  2. VTK 无法解析的外部符号的解决办法
  3. 【错误记录】记录 Android 命令行执行 Java 程序中出现的错误 ( dx 打包 PC 可执行文件报错 | dalvik 命令执行 kotlin 编译的 dex 文件报错 )
  4. Qt工作笔记-Qt creator如何生成dll,以及如何移植到vs上
  5. STM32的SRAM调试
  6. 通过yum安装php7
  7. YII2 rules 规则验证器
  8. Hamilton四元数
  9. Ansible详解(十六)——Ansible配合Redis
  10. 计算机毕业论文答辩申请书,论文答辩申请书范文6篇
  11. 计算机显卡排名,显卡天梯图_显卡性能天梯图_2021笔记本显卡天梯图-中关村在线...
  12. 回炉重造--数据库操作速成记
  13. HighNewTech:元宇宙(metaverse)的简介(多角度理解与探讨)、发展历史、现状与未来
  14. 数据挖掘中所需的概率论与数理统计知识、上
  15. 工程伦理第三章学习笔记2020最新
  16. 力扣:647. 回文子串
  17. 离散数学知识总结 第十一章 几种特殊的图
  18. “神似充气娃娃”的人形机器人,made in China
  19. 大家一起学习用VBA查询数据
  20. 1270: Wooden Sticks [贪心]

热门文章

  1. crossdomain.xml配置不当的利用和解决办法
  2. Java异常处理原则与技巧总结
  3. UML中的6大关系(关联、依赖、聚合、组合、泛化、实现)
  4. Hadoop-HBASE案例分析-Hadoop学习笔记二
  5. Bringing up interface eth0: Device eth0 does not seem to be presen
  6. [译]预留位置队列PRQueue:多线程程序中消息输入队列和消息输出队列保持同序...
  7. 安装包卸载时如何删除安装时写在系统环境变量中的内容
  8. 不允许后退提交数据的方法(抗重复刷新提交)
  9. 用多媒体库 Bass.dll 播放 mp3 [15] - 设置与获取播放速度
  10. [Leedcode][JAVA]第[945]题