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

前言

虽然在.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.  
  4. public void Dispose()

  5. {

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

  7. }

  8.  
  9. #endregion

  10. }

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

using (ResourceUser rec = new ResourceUser())
{

// 具体实现。。。

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

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

静态变量

  1. class Olympics

  2. {

  3. public static Collection<Runner> TryoutRunners;

  4. }

  5.  
  6. class Runner

  7. {

  8. private string _fileName;

  9. private FileStream _fStream;

  10.  
  11. public void GetStats()

  12. {

  13. FileInfo fInfo = new FileInfo(_fileName);

  14. _fStream = _fileName.OpenRead();

  15. }

  16. }


如果你初始化了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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/436774.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【转】分布式事务的常见解决方案

一、事务起步 1. 什么是事务 事务这种东西大家都耳熟能详了&#xff0c;通常指由一组操作组成的一个工作单元&#xff0c;这一整个组合要么全部成功&#xff0c;要么全部失败。 2. 本地事务 在计算机系统中&#xff0c;更多的是通过关系型数据库来控制事务&#xff0c;这是…

java s.charat_Java中s.charAt(index)用于提取字符串s中的特定字符操作

charAt(int index)方法是一个能够用来检索特定索引下的字符的String实例的方法.charAt()方法返回指定索引位置的char值。索引范围为0~length()-1.如: str.charAt(0)检索str中的第一个字符,str.charAt(str.length()-1)检索最后一个字符.警告&#xff1a;在字符串s中越界访问字符…

【转】.NET框架简介

.NET 框架是由微软开发的软件开发平台&#xff0c;其最主要的两个组成部分是公共语言运行时 (CLR) 和框架类库 (FCL)&#xff0c;基础类库 (BCL)是框架类库的一个子集。 .NET 框架简介 下图展示了 .NET 框架的主要结构。 其中&#xff0c;最下层的无疑就是操作系统了。 在 …

java比赛题目_【蓝桥杯2016第七届比赛题目】JAVA A组

1 煤球数目有一堆煤球&#xff0c;堆成三角棱锥形。具体&#xff1a;第一层放1个&#xff0c;第二层3个(排列成三角形)&#xff0c;第三层6个(排列成三角形)&#xff0c;第四层10个(排列成三角形)&#xff0c;....如果一共有100层&#xff0c;共有多少个煤球&#xff1f;请填表…

【转】C#技术漫谈之垃圾回收机制(GC)

摘要&#xff1a;今天我们漫谈C#中的垃圾回收机制&#xff0c;本文将从垃圾回收机制的原理讲起&#xff0c;希望对大家有所帮助。 GC的前世与今生 虽然本文是以.NET作为目标来讲述GC&#xff0c;但是GC的概念并非才诞生不久。早在1958年&#xff0c;由鼎鼎大名的图林奖得主John…

【转】git hub 使用小结

【转自&#xff1a;https://blog.csdn.net/yj310873325/article/details/79255134】 1.创建账号: https://github.com/ 2.下载客户端&#xff1a;https://git-scm.com/download 这是命令行模式&#xff0c;用着比较舒服&#xff0c;不是github的客户端&#xff0c;一路默认安…

【转】细说.NET 中的多线程 (一 概念)

为什么使用多线程 1.使用户界面能够及时响应用户的输入 当某个应用程序在进行大量运算时候&#xff0c;为了保证应用程序能够随时响应客户的输入&#xff0c;这个时候我们往往需要让大量运算和响应用户输入这两个行为在不同的线程中进行。 2.效率原因 应用程序经常需要等待一…

【转】细说.NET中的多线程 (二 线程池)

上一章我们了解到&#xff0c;由于线程的创建&#xff0c;销毁都是需要耗费大量资源和时间的&#xff0c;开发者应该非常节约的使用线程资源。最好的办法是使用线程池&#xff0c;线程池能够避免当前进程中大量的线程导致操作系统不停的进行线程切换&#xff0c;当线程数量到达…

java第二章_零基础学Java第二章

一、第一个代码案例1.1. HelloWorld案例1.1.1 代码执行流程我们写的代码都将以.java开头的文件保存&#xff0c;经过类编译器编译成.class的字节码文件&#xff0c;然后通过解释器翻译与机器交流1.1.1 代码执行流程1. 编写代码步骤首先定义一个类&#xff1a;public class 类名…

java volidate_volidate 学习

一&#xff1a;Volatile 变量具有synchronized的可见性&#xff0c;有序性 特性&#xff0c;但是不具备原子特性二&#xff1a;java memory model(jmm) java 内存模型形象理解见下图Java Memory Modela&#xff1a;java 线程读取共享内存变量流程&#xff1a;线程2 --> JMM …

【转】ABP源码分析一:整体项目结构及目录

ABP是一套非常优秀的web应用程序架构&#xff0c;适合用来搭建集中式架构的web应用程序。 整个Abp的Infrastructure是以Abp这个package为核心模块(core)15个模块(module).其中13个依赖于Abp这个核心包。另外两个包&#xff08;FluentMigration,Web.Resources&#xff09;相对独…

【转】ABP源码分析二:ABP中配置的注册和初始化

一般来说&#xff0c;ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法。执行这个方法前HttpApplication 实例必须存在&#xff0c;也就是说其构造函数必然已完成了执行。 ABP开始的地方就是HttpApplication的构造函数。 如下图一&#xff0c;Abp定义了一…

【转】ABP源码分析三:ABP Module

Abp是基于模块化设计思想进行构建的。开发人员可以将自定义的功能以模块&#xff08;module&#xff09;的形式集成到ABP中。具体的功能都可以设计成一个单独的Module。Abp底层框架提供便捷的方法集成每个Module.下图是所有Abp自带的module.AbpModule是所有Module的基类&#x…

java weka 聚类_简单开源数据挖掘工具weka进行文本聚类

目前非代码的数据挖掘工具很多&#xff0c;但非开源&#xff0c;weka是一款开源软件。只要安装jdk环境就可使用(具体安装jdk可以百度)本文将论述如何不用代码&#xff0c;使用weka操作&#xff0c;通过与文档频数与单词权的特征选择方法进行文本聚类(数据为附件)第一步&#xf…

java 不识别enum_Java enum关键字不识别的快速解决办法

从别人那儿拷贝过来的myeclipse java工程&#xff0c;打开一看标红了一大片&#xff0c;仔细一看&#xff0c;原来是不识别enum关键字&#xff0c;这就有点尴尬了。我自己重新建了一个java工程&#xff0c;测试了下&#xff0c;假如我在新建工程的时候选择javase-1.6&#xff1…

【转】ABP源码分析四:Configuration

核心模块的配置 Configuration是ABP中设计比较巧妙的地方。其通过AbpStartupConfiguration&#xff0c;Castle的依赖注入&#xff0c;Dictionary对象和扩展方法很巧妙的实现了配置中心化。配置中心化是一个支持模块开发的框架必备功能。 ABP中核心功能模块中的一些功能的运行时…

【转】ABP源码分析五:ABP初始化全过程

ABP在初始化阶段做了哪些操作&#xff0c;前面的四篇文章大致描述了一下。 为个更清楚的描述其脉络&#xff0c;做了张流程图以辅助说明。其中每一步都涉及很多细节&#xff0c;难以在一张图中全部表现出来。每一步的细节&#xff08;会涉及到较多接口&#xff0c;类&#xff0…

【转】ABP源码分析六:依赖注入的实现

ABP的依赖注入的实现有一个本质两个途径&#xff1a;1.本质上是依赖于Castle这个老牌依赖注入的框架。2.一种实现途径是通过实现IConventionalDependencyRegistrar的实例定义注入的约定&#xff08;规则&#xff09;&#xff0c;然后通过IocManager来读取这个规则完成依赖注入。…

【转】ABP源码分析七:Setting 以及 Mail

本文主要说明Setting的实现以及Mail这个功能模块如何使用Setting. 首先区分一下ABP中的Setting和Configuration。 Setting一般用于需要通过外部配置文件&#xff08;或数据库&#xff09;设置的简单类型数据&#xff08;一般就是字符串&#xff09;&#xff0c;比如SMTP HOST.…

【转】ABP源码分析八:Logger集成

ABP使用Castle日志记录工具&#xff0c;并且可以使用不同的日志类库&#xff0c;比如&#xff1a;Log4Net, NLog, Serilog... 等等。对于所有的日志类库&#xff0c;Castle提供了一个通用的接口来实现&#xff0c;我们可以很方便的处理各种特殊的日志库&#xff0c;而且当业务需…