<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">博客园_.Net C# Asp.Net 技术博客</title><subtitle type="text">.NET技术 Blog .NET c# software 穷则变，变则通，通则久 寂寞平淡</subtitle><id>http://feed.cnblogs.com/blog/u/14943/rss</id><updated>2011-12-19T10:12:22Z</updated><author><name>玉开</name><uri>http://www.cnblogs.com/yukaizhao/</uri></author><generator>CNBlogs BlogServer</generator><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yukaizhao/"/><link rel="self" type="application/atom+xml" href="http://feed.cnblogs.com/blog/u/14943/rss"/><entry><id>http://www.cnblogs.com/yukaizhao/archive/2011/12/19/html5-loading.html</id><title type="text">Html5 loading 来了</title><summary type="text">Html5在移动设备上表现抢眼，几乎所有稍微高端一点的设备（乔帮主的iPad，iPhone和Andriod的平板手机等）的浏览器都支持Html5。而且据我个人的测试这些支持html5的设备对canvas标签的支持是相当的好</summary><published>2011-12-19T10:05:00Z</published><updated>2011-12-19T10:05:00Z</updated><author><name>玉开</name><uri>http://www.cnblogs.com/yukaizhao/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yukaizhao/archive/2011/12/19/html5-loading.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yukaizhao/archive/2011/12/19/html5-loading.html"/><content type="html">&lt;p&gt;Html5在移动设备上表现抢眼，几乎所有稍微高端一点的设备（乔帮主的iPad，iPhone和Andriod的平板手机等）的浏览器都支持Html5。而且据我个人的测试这些支持html5的设备对canvas标签的支持是相当的好。&lt;/p&gt;&#xD;
&lt;p&gt;所以就看了一点html5的东东，并研究了一下canvas标签的用法。如果你从来没有用过canvas标签，请参考&lt;a href="http://images.cnblogs.com/cnblogs_com/yukaizhao/133619/o_HTML5_Canvas_Cheat_Sheet.png" target="_blank"&gt;Html5 canvas cheat sheet&lt;/a&gt;。&lt;/p&gt;&#xD;
&lt;p&gt;大家都知道web2.0以来大量的使用ajax，loading的小图标也有很多很多种，甚至还有专门提供loading图片的网站。所以我就想能不能让html5解决一下这个以前用gif文件解决的问题。没想到非常的简单，只用了不到一小时的时间就搞定了两个，而且这样做出来的loading图标是可定制的，既可以定制颜色，也可以定制大小等属性。&lt;/p&gt;&#xD;
&lt;p&gt;废话少叙，我们先来看下这两个loading图标的效果，注意，如果你用的是PC，请使用Firefox，Opera或者IE9看此效果。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;第一个带着小尾巴转动的loading图标画图的思路是，首先画一个圆，然后在圆的边上按顺序画大小逐渐减小的小圆点，在每次刷新画布时改变这一系列的小圆点在大圆边上的位置。&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;&amp;lt;!doctype html&amp;gt;&#xD;
&amp;lt;html&amp;gt;&#xD;
  &amp;lt;head&amp;gt;&#xD;
    &amp;lt;meta http-equiv="content-type" content="GBK"/&amp;gt;&#xD;
    &amp;lt;title&amp;gt;loading&amp;lt;/title&amp;gt;&#xD;
    &amp;lt;script type="text/javascript"&amp;gt;&#xD;
    /*&#xD;
    html5 loading 控件&#xD;
    作者：玉开 博客：http://www.cnblogs.com/yukaizhao/&#xD;
    发布或使用此控件，请保留作者声明&#xD;
    */&#xD;
    function loading(canvas,options){&#xD;
      this.canvas = canvas;&#xD;
      if(options){&#xD;
        this.radius = options.radius||12;&#xD;
        this.circleLineWidth = options.circleLineWidth||4;&#xD;
        this.circleColor = options.circleColor||'lightgray';&#xD;
        this.dotColor = options.dotColor||'gray';&#xD;
      }else{      &#xD;
        this.radius = 12;&#xD;
        this.circelLineWidth = 4;&#xD;
        this.circleColor = 'lightgray';&#xD;
        this.dotColor = 'gray';&#xD;
      }&#xD;
    }&#xD;
    loading.prototype = {&#xD;
      show:function (){&#xD;
        var canvas = this.canvas;&#xD;
        if(!canvas.getContext)return;&#xD;
        if(canvas.__loading)return;&#xD;
        canvas.__loading = this;&#xD;
        var ctx = canvas.getContext('2d');&#xD;
        var radius = this.radius;      &#xD;
        var rotators = [{angle:0,radius:1.5},{angle:3/radius,radius:2},{angle:7/radius,radius:2.5},{angle:12/radius,radius:3}];      &#xD;
        var me = this;&#xD;
        canvas.loadingInterval = setInterval(function(){&#xD;
          ctx.clearRect(0,0,canvas.width,canvas.height);         &#xD;
          var lineWidth = me.circleLineWidth;&#xD;
          var center = {x:canvas.width/2 - radius,y:canvas.height/2-radius};          &#xD;
          ctx.beginPath();&#xD;
          ctx.lineWidth = lineWidth;&#xD;
          ctx.strokeStyle = me.circleColor;&#xD;
          ctx.arc(center.x,center.y,radius,0,Math.PI*2);&#xD;
          ctx.closePath();&#xD;
          ctx.stroke();&#xD;
          for(var i=0;i&amp;lt;rotators.length;i++){        &#xD;
            var rotatorAngle = rotators[i].currentAngle||rotators[i].angle;            &#xD;
            //在圆圈上面画小圆&#xD;
            var rotatorCenter = {x:center.x-(radius)*Math.cos(rotatorAngle) ,y:center.y-(radius)*Math.sin(rotatorAngle)};            &#xD;
            var rotatorRadius = rotators[i].radius;&#xD;
            ctx.beginPath();&#xD;
            ctx.fillStyle = me.dotColor;&#xD;
            ctx.arc(rotatorCenter.x,rotatorCenter.y,rotatorRadius,0,Math.PI*2);&#xD;
            ctx.closePath();&#xD;
            ctx.fill();&#xD;
            rotators[i].currentAngle = rotatorAngle+4/radius;&#xD;
          }&#xD;
        },50);&#xD;
      },&#xD;
      hide:function(){&#xD;
        var canvas = this.canvas;&#xD;
        canvas.__loading = false;&#xD;
        if(canvas.loadingInterval){&#xD;
          window.clearInterval(canvas.loadingInterval);&#xD;
        }&#xD;
        var ctx = canvas.getContext('2d');&#xD;
        if(ctx)ctx.clearRect(0,0,canvas.width,canvas.height);&#xD;
      }&#xD;
    };&#xD;
    &#xD;
    &amp;lt;/script&amp;gt;&#xD;
  &amp;lt;/head&amp;gt;&#xD;
  &amp;lt;body&amp;gt;&#xD;
    &amp;lt;canvas id="canvas" width="300" height="100" style="border:1px solid #69c"&amp;gt;&amp;lt;/canvas&amp;gt;&#xD;
    &amp;lt;p&amp;gt;&#xD;
    &amp;lt;input type="button" onclick="loadingObj.hide()" value="HideLoading"/&amp;gt;&#xD;
    &amp;lt;input type="button" onclick="loadingObj.show()" value="showLoading"/&amp;gt;&#xD;
    &amp;lt;/p&amp;gt;&#xD;
    &amp;lt;script&amp;gt;&#xD;
    var loadingObj = new loading(document.getElementById('canvas'),{radius:8,circleLineWidth:3});&#xD;
    loadingObj.show();&#xD;
    &amp;lt;/script&amp;gt;&#xD;
  &amp;lt;/body&amp;gt;&#xD;
&amp;lt;/html&amp;gt;&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;第二个较为简单，在一个圆环上有一个相同圆心相同半径的圆弧在不停的转动。画图的步骤是首先画一个圆环，然后画一个不同颜色相同圆心半径的圆弧，在每次刷新画布时改变圆弧的起始角度。&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;&amp;lt;!doctype html&amp;gt;&#xD;
&amp;lt;html&amp;gt;&#xD;
&amp;lt;head&amp;gt;&#xD;
  &amp;lt;meta http-equiv="content-type" content="text/html;charset=gbk"/&amp;gt;&#xD;
  &amp;lt;title&amp;gt;loading&amp;lt;/title&amp;gt;&#xD;
  &amp;lt;script&amp;gt;&#xD;
   /*&#xD;
    html5 loading 控件&#xD;
    作者：玉开 博客：http://www.cnblogs.com/yukaizhao/&#xD;
    发布或使用此控件，请保留作者声明&#xD;
    */&#xD;
    function loading(canvas,options){&#xD;
      this.canvas = canvas;&#xD;
      if(options){&#xD;
        this.radius = options.radius||12;&#xD;
        this.circleLineWidth = options.circleLineWidth||4;&#xD;
        this.circleColor = options.circleColor||'lightgray';&#xD;
        this.moveArcColor = options.moveArcColor||'gray';&#xD;
      }else{      &#xD;
        this.radius = 12;&#xD;
        this.circelLineWidth = 4;&#xD;
        this.circleColor = 'lightgray';&#xD;
        this.moveArcColor = 'gray';&#xD;
      }&#xD;
    }&#xD;
    loading.prototype = {&#xD;
      show:function (){&#xD;
        var canvas = this.canvas;&#xD;
        if(!canvas.getContext)return;&#xD;
        if(canvas.__loading)return;&#xD;
        canvas.__loading = this;&#xD;
        var ctx = canvas.getContext('2d');&#xD;
        var radius = this.radius;      &#xD;
        var me = this;&#xD;
        var rotatorAngle = Math.PI*1.5;&#xD;
        var step = Math.PI/6;&#xD;
        canvas.loadingInterval = setInterval(function(){&#xD;
          ctx.clearRect(0,0,canvas.width,canvas.height);         &#xD;
          var lineWidth = me.circleLineWidth;&#xD;
          var center = {x:canvas.width/2 - radius,y:canvas.height/2-radius};          &#xD;
          ctx.beginPath();&#xD;
          ctx.lineWidth = lineWidth;&#xD;
          ctx.strokeStyle = me.circleColor;&#xD;
          ctx.arc(center.x,center.y,radius,0,Math.PI*2);&#xD;
          ctx.closePath();&#xD;
          ctx.stroke();&#xD;
          //在圆圈上面画小圆&#xD;
          ctx.beginPath();&#xD;
          ctx.strokeStyle = me.moveArcColor;&#xD;
          ctx.arc(center.x,center.y,radius,rotatorAngle,rotatorAngle+Math.PI*.45);&#xD;
          ctx.stroke();&#xD;
          rotatorAngle+=step;&#xD;
         &#xD;
        },50);&#xD;
      },&#xD;
      hide:function(){&#xD;
        var canvas = this.canvas;&#xD;
        canvas.__loading = false;&#xD;
        if(canvas.loadingInterval){&#xD;
          window.clearInterval(canvas.loadingInterval);&#xD;
        }&#xD;
        var ctx = canvas.getContext('2d');&#xD;
        if(ctx)ctx.clearRect(0,0,canvas.width,canvas.height);&#xD;
      }&#xD;
    };&#xD;
    &#xD;
    &amp;lt;/script&amp;gt;&#xD;
  &amp;lt;/head&amp;gt;&#xD;
  &amp;lt;body&amp;gt;&#xD;
    &amp;lt;canvas id="canvas" width="300" height="100" style="border:1px solid #69c"&amp;gt;您的浏览器不支持html5哟&amp;lt;/canvas&amp;gt;&#xD;
    &amp;lt;p&amp;gt;&#xD;
    &amp;lt;input type="button" onclick="loadingObj.hide()" value="HideLoading"/&amp;gt;&#xD;
    &amp;lt;input type="button" onclick="loadingObj.show()" value="showLoading"/&amp;gt;&#xD;
    &amp;lt;/p&amp;gt;&#xD;
    &amp;lt;script&amp;gt;&#xD;
    var loadingObj = new loading(document.getElementById('canvas'),{radius:8,circleLineWidth:3});&#xD;
    loadingObj.show();&#xD;
    &amp;lt;/script&amp;gt;&#xD;
  &amp;lt;/body&amp;gt;&#xD;
&amp;lt;/html&amp;gt;&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;目前从移动设备对Html5的支持来看，html5大有可为。&lt;/p&gt;&#xD;
&lt;p&gt;天下大势，合久必分，分久必和。PC开发时web应用在很大程度上统一了客户端程序；而现在移动开发使用不同的系统不同的语言，将来大多数应用必然会统一到一种语言。这种语言必然是html5+javascript.&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yukaizhao/aggbug/2293707.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/12/19/html5-loading.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yukaizhao/archive/2011/12/08/decimal-double-format-datetime-format.html</id><title type="text">.Net中的数字和日期格式化规则助记词</title><summary type="text">将数字格式化规则编成了一小段词，帮助你记忆数字格式化的规则。还有日期格式化，进来看看一起改进呀</summary><published>2011-12-08T01:16:00Z</published><updated>2011-12-08T01:16:00Z</updated><author><name>玉开</name><uri>http://www.cnblogs.com/yukaizhao/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yukaizhao/archive/2011/12/08/decimal-double-format-datetime-format.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yukaizhao/archive/2011/12/08/decimal-double-format-datetime-format.html"/><content type="html">&lt;p&gt;格式化可以通过string.Format方法或者直接使用xx.ToString("")方法执行。格式化的规则不太好记，本文通过简单的词话，可以帮你记忆这些规则&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;&amp;nbsp;1. 数字格式化&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;规则很简单都是一个字母表示格式化的种类后面可以可选的跟一个数字表示要保留的小数位数。&lt;/p&gt;&#xD;
&lt;p&gt;字母就那么几个：C、D、E、F、G、N、P、R、X 大小写是通用的&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/11387/2011120809122965.png" alt="" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;参考资料： &lt;a href="http://msdn.microsoft.com/en-us/library/dwhawy9k.aspx%20"&gt;http://msdn.microsoft.com/en-us/library/dwhawy9k.aspx&amp;nbsp;&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;2. DateTime格式化&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;2011-12-08 13：09：24.211 +8：00&amp;nbsp;&lt;/p&gt;&#xD;
&lt;p&gt;年份是y：&lt;/p&gt;&#xD;
&lt;p&gt;一y年份个位&lt;/p&gt;&#xD;
&lt;p&gt;yy年份后两位&lt;/p&gt;&#xD;
&lt;p&gt;yyy年份后三位&lt;/p&gt;&#xD;
&lt;p&gt;yyyy四位年份&lt;/p&gt;&#xD;
&lt;p&gt;yyyyy四位年份前加0&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;大M表月份，小于10不加前导0&lt;/p&gt;&#xD;
&lt;p&gt;MM表月份，小于10月要加前导0&lt;/p&gt;&#xD;
&lt;p&gt;三个M月份名，要用简称得记住&lt;/p&gt;&#xD;
&lt;p&gt;四个M月份名，要用全称不能忘&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;一d表示是几号，10号之前不加0&lt;/p&gt;&#xD;
&lt;p&gt;dd表示是几号，10号之前要加0&lt;/p&gt;&#xD;
&lt;p&gt;ddd表示星期几，名称简写得记住&lt;/p&gt;&#xD;
&lt;p&gt;dddd表示星期几，要用全名不能忘&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;小写h表小时，12进制不能忘&lt;/p&gt;&#xD;
&lt;p&gt;一个h不能前加0，两个h如有必要前导0&lt;/p&gt;&#xD;
&lt;p&gt;大小H表小时，24进制要记牢&lt;/p&gt;&#xD;
&lt;p&gt;一个H不能前加0，两个H如有必要前导0&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;小写m表分钟，一个不必前导0，两个若有必要前导0&lt;/p&gt;&#xD;
&lt;p&gt;小写s表示秒，一个不必前导0，两个若有必要前导0&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;f表示十分之一秒数&lt;/p&gt;&#xD;
&lt;p&gt;ff表示百分之秒数&lt;/p&gt;&#xD;
&lt;p&gt;fff表示毫秒数&lt;/p&gt;&#xD;
&lt;p&gt;ffff表示十分之一毫秒数&lt;/p&gt;&#xD;
&lt;p&gt;依次类推到7f&lt;/p&gt;&#xD;
&lt;p&gt;大写小写有差别&lt;/p&gt;&#xD;
&lt;p&gt;如果是0，大写忽略小写有&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;还有两个不常用&lt;/p&gt;&#xD;
&lt;p&gt;gg表示上下午&lt;/p&gt;&#xD;
&lt;p&gt;k、z意思是时区&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;参考资料：&lt;a href="http://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx"&gt;http://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;希望这篇随笔能够帮助你记忆数字时间的格式化规则。说是词其实就是随心而做，有的句子是不押韵的，希望你帮助改进它。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yukaizhao/aggbug/2280202.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/12/08/decimal-double-format-datetime-format.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yukaizhao/archive/2011/12/06/use-gc-effective-4.html</id><title type="text">【译】让垃圾回收器高效工作（四）</title><summary type="text">这篇文章我们来谈谈垃圾回收器和程序的虚拟内存、物理内存之间的关系。再谈谈怎样判断你的托管堆是否是健康的；为什么在机器还有大量内存的情况下程序会抛出OutofMemoryException</summary><published>2011-12-06T01:00:00Z</published><updated>2011-12-06T01:00:00Z</updated><author><name>玉开</name><uri>http://www.cnblogs.com/yukaizhao/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yukaizhao/archive/2011/12/06/use-gc-effective-4.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yukaizhao/archive/2011/12/06/use-gc-effective-4.html"/><content type="html">&lt;p&gt;这篇文章我们来谈谈垃圾回收器和程序的虚拟内存、物理内存之间的关系。再谈谈怎样判断你的托管堆是否是健康的；为什么在机器还有大量内存的情况下程序会抛出OutofMemoryException。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;垃圾回收和物理内存虚拟内存之间的关系：&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;如果你对这个话题已经了如指掌，请跳过这一段。&lt;/p&gt;&#xD;
&lt;p&gt;GC需要分配段，有关段的解释请参考《&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/28/use-gc-effective-1.html"&gt;让垃圾回收器高效工作（一）&lt;/a&gt;》。GC调用VirtualAlloc来分配段空间。这意味着如果你的进程中没有足够的连续空间，分配就失败了。这是一种GC抛出OutOfMemeoryException的合法的情况（精确的说，GC没有抛出异常，抛出异常是执行引擎干的，GC只是在分配失败时返回了NULL）。&lt;/p&gt;&#xD;
&lt;p&gt;经常有人问我这样的问题：&amp;ldquo;为什么我程序的托管堆只使用了X MB的内存，运行时抛出了OutofMemoryException呢？&amp;rdquo; 其中XMB比2GB小得多。&lt;/p&gt;&#xD;
&lt;p&gt;记住在.Net程序中有一些内存不是GC消耗的。GC和其他一些东西一样是在竞争虚拟内存的空间。比如：你进程中载入的模块需要占用虚拟内存；有些模块直接调用本机代码分配内存也会消耗虚拟内存（VirtualAlloc，HeapAlloc，C++的new等等）。CLR本身也会有些不通过GC分配消耗的内存，比如jitted代码，一些CLR需要的数据结构等等。通常CLR需要的内存是相当小的。你可以通过SOS的!eeheap命令来查看CLR使用的内存信息。下面是运行此命令的一个输出样例，一些说明用中括号括起来了。&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;0:119&amp;gt; !eeheap&#xD;
Loader Heap:&#xD;
--------------------------------------&#xD;
System Domain: 79bbd970&#xD;
LowFrequencyHeap: Size: 0x0(0)bytes.&#xD;
HighFrequencyHeap: 007e0000(10000:2000) Size: 0x2000(8192)bytes.&#xD;
StubHeap: 007d0000(10000:7000) Size: 0x7000(28672)bytes.&#xD;
Virtual Call Stub Heap:&#xD;
  IndcellHeap: Size: 0x0(0)bytes.&#xD;
  LookupHeap: Size: 0x0(0)bytes.&#xD;
  ResolveHeap: Size: 0x0(0)bytes.&#xD;
  DispatchHeap: Size: 0x0(0)bytes.&#xD;
  CacheEntryHeap: Size: 0x0(0)bytes.&#xD;
Total size: 0x9000(36864)bytes&#xD;
--------------------------------------&#xD;
Shared Domain: 79bbdf18&#xD;
...&#xD;
Total size: 0xe000(57344)bytes&#xD;
--------------------------------------&#xD;
Domain 1: 151a18&#xD;
...&#xD;
Total size: 0x147000(1339392)bytes&#xD;
--------------------------------------&#xD;
Jit code heap:&#xD;
LoaderCodeHeap: 23980000(10000:7000) Size: 0x7000(28672)bytes.&#xD;
...&#xD;
Total size: 0x87000(552960)bytes &#xD;
[jited代码消耗的空间非常小 &amp;ndash;这是一个相当大的程序]&#xD;
--------------------------------------&#xD;
Module Thunk heaps:&#xD;
Module 78c40000: Size: 0x0(0)bytes.&#xD;
...&#xD;
Total size: 0x0(0)bytes&#xD;
--------------------------------------&#xD;
Module Lookup Table heaps:&#xD;
Module 78c40000: Size: 0x0(0)bytes.&#xD;
...&#xD;
Total size: 0x0(0)bytes&#xD;
--------------------------------------&#xD;
Total LoaderHeap size: 0x1e5000(1986560)bytes [total Loader heap takes &amp;lt; 2MB]&#xD;
=======================================&#xD;
Number of GC Heaps: 4&#xD;
------------------------------&#xD;
Heap 0 (0015ad08)&#xD;
generation 0 starts at 0x49521f8c&#xD;
generation 1 starts at 0x494d7f64&#xD;
generation 2 starts at 0x007f0038&#xD;
ephemeral segment allocation context: none&#xD;
[The first 2 segments are read only segments for frozen strings which is why they look a bit odd compared to other segments. The addresses for begin and segment are very different and usually they are tiny segments (unless you have tons and tons of frozen strings)]&#xD;
 segment    begin allocated     size&#xD;
00178250 7a80d84c  7a82f1cc 0x00021980(137600)&#xD;
00161918 78c50e40  78c7056c 0x0001f72c(128812)&#xD;
007f0000 007f0038  047eed28 0x03ffecf0(67103984)&#xD;
3a120000 3a120038  3a3e84f8 0x002c84c0(2917568)&#xD;
46120000 46120038  49e05d04 0x03ce5ccc(63855820)&#xD;
Large object heap starts at 0x107f0038&#xD;
 segment    begin allocated     size&#xD;
107f0000 107f0038  11ad0008 0x012dffd0(19791824)&#xD;
20960000 20960038  224f7970 0x01b97938(28932408)&#xD;
Heap Size  0xae65830(182868016)&#xD;
------------------------------&#xD;
Heap 1 (0015b688)&#xD;
...&#xD;
Heap Size  0x7f213bc(133305276)&#xD;
------------------------------&#xD;
Heap 2 (0015c008)&#xD;
...&#xD;
Heap Size  0x7ada9ac(128821676)&#xD;
------------------------------&#xD;
Heap 3 (0015cdc8)&#xD;
...&#xD;
Heap Size  0x764c214(124043796)&#xD;
------------------------------&#xD;
GC Heap Size  0x21ead7ac(569038764) [托管堆消耗的内存大约为540MB]&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;程序在这一刻发生了OutOfMemoryException。让我们看下这一个时间点的空闲虚拟内存块。你可以使用!vadump命令或者其他一些工具来分析虚拟内存使用情况。我非常喜欢用!address命令，它可以很好的输出最大的空闲区。&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;0:119&amp;gt; !address&#xD;
...&#xD;
Largest free region: Base 54000000 - Size 03b60000&#xD;
&#xD;
0:119&amp;gt; ? 03b60000&#xD;
Evaluate expression: 62259200 = 03b60000&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;可以看到最大的空闲区域小于64M，这个就是发生OOM的原因了。垃圾回收器需要分配一个新的段，但是却没有足够的空间了（这个程序是运行在Server GC模式下的，所以一个段是64M）。&lt;/p&gt;&#xD;
&lt;p&gt;如果你在程序中经常载入或者卸载不同大小的模块虚拟内存空间会有很多碎片，这很糟糕。比如：COM dlls在不用时卸载，然后过一会又重新载入；web程序有很多小的dll（一个10k的dll需要占用64k的虚拟内存）。&lt;/p&gt;&#xD;
&lt;p&gt;除了分配段之外垃圾回收器和虚拟内存就没有任何关系了。而物理内存就是另一回事儿了。如我在前文提到的，如果你的机器的物理内存很低就会触发垃圾回收。这时候垃圾回收器变得很有侵略性。在物理内存低的时候GC性能计数器0代，1代，2代回收次数之间的比例会接近1. 如果GC没有足够的内存来满足分配请求也会抛出OutofMemoryException。&lt;/p&gt;&#xD;
&lt;p&gt;现在机器有超过2G的内存已经不算什么了。不要过分的碎片化虚拟内存是很重要的。然而，如果如果你的程序运行在64位服务器上，虚拟内存空间很大，而物理内存有可能会成为限制因素。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;你的托管堆是正常的吗？&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;我们先来看看如何获得托管堆性能数据：&lt;br /&gt;&lt;strong&gt;1. 收集性能计数器日志数据&lt;/strong&gt;&lt;br /&gt;我建议你在使用GC性能计数器时要每隔一个很小的间隔（比如1秒）记录一次数据，然后连续收集几分钟。通常情况下这要比收集几个小时，每隔5-10秒收集一次数据得到的样本更有意义。然而，如果你托管程序的问题是随机出现的，那没有选择你必须要长时间收集性能计数器数据。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;2. 通过生成dump文件分析&lt;/strong&gt;&lt;br /&gt;如果要分析托管堆的问题最好抓一个完整的dump，mini dump通常情况下是没用的&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;3. 使用CLRProfiler观察&lt;/strong&gt;&lt;br /&gt;CLRProfiler是一个很重的工具，它不能附加到进程。有关如何使用这个工具，请看它的文档，文档中有很详细的说明，还有几个测试用的例子&lt;/p&gt;&#xD;
&lt;p&gt;当你观察托管堆时，收集什么样的数据是有意义的呢？&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;1. Time in GC&lt;/strong&gt;&lt;br /&gt;如果%Time in GC的值很小，那么这个参数对分析分配相关的问题是没用的。当%Time in GC的值很大通常意味着程序做了太多的2代回收并且每次都花了很长时间。这时候就该详细的分析一下你的代码找出为什么会有这么多的2代回收，是否有办法改变分配的模式来减少2代回收的时间了。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;2. 托管堆增长模式&lt;/strong&gt;&lt;br /&gt;如果你有一个需要长时间运行的服务应用。如果你观察到托管堆不停的增长，那通常是一个坏兆头。通常情况下服务程序要把每一个处理完的请求相关数据都清理掉，如果不是这样的话那你的程序很有可能有内存泄漏的情况，你必须查一下原因了---否则程序会报OutofMemoryException。出现这种情况时我们会在第一时间要求开发人员去修复。&lt;/p&gt;&#xD;
&lt;p&gt;观察性能计数器也可以发现一些明显的问题，比如：如果# of GC Handles持续增长，就表明你的程序中肯定存在一个句柄泄漏。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;3. 各代回收次数之间的比例&lt;/strong&gt;&lt;br /&gt;我经常告诉人们一个健康的托管堆2代回收次数和1代回收次数之比是1：10；如果你发现他们的比例是1：1，就需要看看你的程序了。如果托管堆很大，做一次2代回收就需要花费很长时间了，理想状况下我们希望2代回收尽可能的少。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;4. 碎片（Fragmentation）&lt;/strong&gt;&lt;br /&gt;碎片是托管堆上的空闲空间，你可以通过下面的sos命令查看它：&lt;br /&gt;!dump &amp;ndash;type Free &amp;ndash;stat&lt;br /&gt;当然程序中的碎片越小越好，但是这几乎不可能。因为只要你使用IO或者其他可能固定内存地址的对象就有可能在托管堆上形成内存碎片。不同代上的碎片对程序性能的影响是不同的：&lt;/p&gt;&#xD;
&lt;p&gt;0代堆上出现碎片是好的，因为我们需要在0代堆上分配对象，在分配对象时会占用这些碎片。程序碎片都出现在0代堆上是一种非常理想的状态。你可以为!dumpheap命令指定开始和结束地址来查看0代堆上有多少空间，我们看下前面例子dump的情况：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;Heap 0 (0015ad08)&#xD;
generation 0 starts at 0x49521f8c&#xD;
generation 1 starts at 0x494d7f64&#xD;
generation 2 starts at 0x007f0038&#xD;
ephemeral segment allocation context: none&#xD;
segment    begin  allocated    size&#xD;
00178250 7a80d84c  7a82f1cc 0x00021980(137600)&#xD;
00161918 78c50e40  78c7056c 0x0001f72c(128812)&#xD;
007f0000 007f0038  047eed28 0x03ffecf0(67103984)&#xD;
3a120000 3a120038  3a3e84f8 0x002c84c0(2917568)&#xD;
46120000 46120038  49e05d04 0x03ce5ccc(63855820) [the last one is always the ephemeral segment]&#xD;
&#xD;
0:119&amp;gt; ? 49e05d04-0x49521f8c&#xD;
Evaluate expression: 9321848 = 008e3d78 [gen0 is about 9MB]&#xD;
&#xD;
0:119&amp;gt; !dumpheap -type Free -stat 0x49521f8c 49e05d04 &#xD;
------------------------------&#xD;
Heap 0&#xD;
total 409 objects&#xD;
------------------------------&#xD;
Heap 1&#xD;
total 0 objects&#xD;
------------------------------&#xD;
Heap 2&#xD;
total 0 objects&#xD;
------------------------------&#xD;
Heap 3&#xD;
total 0 objects&#xD;
------------------------------&#xD;
total 409 objects&#xD;
Statistics:&#xD;
      MT    Count TotalSize Class Name&#xD;
0015a498      409   7296540      Free&#xD;
Total 409 objects&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;从上面的输出可以看到0代堆的大小约为9M，其中7M空闲空间。&lt;/p&gt;&#xD;
&lt;p&gt;可以说大对象堆上出现碎片是设计设然，因为我们不会在大对象堆上做碎片整理。这不意味着在大对象堆上分配对象和使用NT 堆管理器的分配方式相同。根据GC的工作特性，被释放的对象会调整到一起组合成一个大的空间可以用来满足大对象分配请求。&lt;/p&gt;&#xD;
&lt;p&gt;碎片出现在2代堆和1代堆上的情况是最糟糕的。如果在一次垃圾回收之后1代堆和2代堆上仍然有很多空闲内存就可以肯定程序在托管内存的使用上是存在问题的。如果你的程序出现这种问题，请参考《&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/30/use-gc-effective-3.html" target="_blank"&gt;让垃圾回收器高效工作三&lt;/a&gt;》&lt;/p&gt;&#xD;
&lt;p&gt;对于2代堆来说如果碎片比例小于20%就可以认为是非常好的。&lt;/p&gt;&#xD;
&lt;p&gt;原文：&lt;a href="http://blogs.msdn.com/b/maoni/archive/2005/05/06/415296.aspx" target="_blank"&gt;http://blogs.msdn.com/b/maoni/archive/2005/05/06/415296.aspx&lt;/a&gt;&lt;br /&gt;原作者：Maoni Stephens&lt;/p&gt;&#xD;
&lt;p&gt;相关随笔：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/30/use-gc-effective-3.html"&gt;让垃圾回收器高效工作（三）&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/29/use-gc-effective-2.html"&gt;让垃圾回收器高效工作（二）&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/28/use-gc-effective-1.html"&gt;让垃圾回收器高效工作（一）&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/23/dot_net_GC_1.html"&gt;.Net 垃圾回收机制原理（一）&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/25/dot_net_GC_2.html"&gt;.Net 垃圾回收机制原理（二）&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/21/dot_net_gc_large_object_heap.html"&gt;.Net 垃圾回收和大对象处理&lt;/a&gt;&amp;nbsp;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yukaizhao/aggbug/2277483.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/12/06/use-gc-effective-4.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yukaizhao/archive/2011/12/02/net-web-config-costom-config-implement.html</id><title type="text">在Web.config或App.config中的添加自定义配置</title><summary type="text">本文介绍了如何在配置文件中自定义配置一个简单的类，配置复杂类型，如何配置集合类，如何使用系统自带的键值对集合配置类似appSettings的配置节以及如何配置sectionGroup等</summary><published>2011-12-02T09:26:00Z</published><updated>2011-12-02T09:26:00Z</updated><author><name>玉开</name><uri>http://www.cnblogs.com/yukaizhao/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yukaizhao/archive/2011/12/02/net-web-config-costom-config-implement.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yukaizhao/archive/2011/12/02/net-web-config-costom-config-implement.html"/><content type="html">&lt;p&gt;.Net中的System.Configuration命名空间为我们在web.config或者app.config中自定义配置提供了完美的支持。最近看到一些项目中还在自定义xml文件做程序的配置，所以忍不住写一篇用系统自定义配置的随笔了。&lt;/p&gt;&#xD;
&lt;p&gt;如果你已经对自定义配置了如指掌，请忽略这篇文章。&lt;/p&gt;&#xD;
&lt;p&gt;言归正传，我们先来看一个最简单的自定义配置&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;&amp;lt;?xml version="1.0" encoding="utf-8" ?&amp;gt;&#xD;
&amp;lt;configuration&amp;gt;&#xD;
  &amp;lt;configSections&amp;gt;&#xD;
    &amp;lt;section name="simple" type="ConfigExample.Configuration.SimpleSection,ConfigExample"/&amp;gt;&#xD;
  &amp;lt;/configSections&amp;gt;&#xD;
  &amp;lt;simple maxValue="20" minValue="1"&amp;gt;&amp;lt;/simple&amp;gt;&#xD;
&amp;lt;/configuration&amp;gt;&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;在配置文件中使用自定义配置，需要在configSections中添加一个section元素，并制定此section元素对应的类型和名字。然后再在configuration根节点下面添加此自定义配置，如上例中的simple节点。simple节点只有两个整形数的属性maxValue和minValue。&lt;/p&gt;&#xD;
&lt;p&gt;要在程序中使用自定义配置我们还需要实现存取这个配置块的类型，一般需要做如下三件事：&lt;br /&gt;1. 定义类型从System.Configuration.ConfigurationSection继承&lt;br /&gt;2. 定义配置类的属性，这些属性需要用ConfigurationProperty特性修饰，并制定属性在配置节中的名称和其他一些限制信息&lt;br /&gt;3. 通过基类的string索引器实现属性的get ，set&lt;/p&gt;&#xD;
&lt;p&gt;非常简单和自然，如下是上面配置类的实现：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;public class SimpleSection:System.Configuration.ConfigurationSection&#xD;
{&#xD;
    [ConfigurationProperty("maxValue",IsRequired=false,DefaultValue=Int32.MaxValue)]&#xD;
    public int MaxValue&#xD;
    {&#xD;
        get&#xD;
        {&#xD;
            return  (int)base["maxValue"];&#xD;
        }&#xD;
        set&#xD;
        {&#xD;
            base["maxValue"] = value;&#xD;
        }&#xD;
    }&#xD;
&#xD;
    [ConfigurationProperty("minValue",IsRequired=false,DefaultValue=1)]&#xD;
    public int MinValue&#xD;
    {&#xD;
        get { return (int) base["minValue"];}&#xD;
        set { base["minValue"] = value; }&#xD;
    }&#xD;
&#xD;
&#xD;
    [ConfigurationProperty("enabled",IsRequired=false,DefaultValue=true)]&#xD;
    public bool Enable&#xD;
    {&#xD;
        get&#xD;
        {&#xD;
            return (bool)base["enabled"];&#xD;
        }&#xD;
        set&#xD;
        {&#xD;
            base["enabled"] = value;&#xD;
        }&#xD;
    }&#xD;
}&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;这样子一个简单的配置类就完成了，怎么在程序中使用这个配置呢？需要使用ConfigurationManager类（要引用System.configuration.dll这个dll只有在.Net2.0之后的版本中才有）的GetSection方法获得配置就可以了。如下代码：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;SimpleSection simple = ConfigurationManager.GetSection("simple") as SimpleSection;&#xD;
Console.WriteLine("simple minValue={0} maxValue = {1}",simple.MinValue,simple.MaxValue);&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;这个配置类太过简陋了，可能有时候我们还需要更复杂的构造，比如在配置类中使用类表示一组数据，下面我们看一个稍微复杂一点的自定义配置&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;&amp;lt;?xml version="1.0" encoding="utf-8" ?&amp;gt;&#xD;
&amp;lt;configuration&amp;gt;&#xD;
  &amp;lt;configSections&amp;gt;&#xD;
    &amp;lt;section name="complex" type="ConfigExample.Configuration.ComplexSection,ConfigExample"/&amp;gt;&#xD;
  &amp;lt;/configSections&amp;gt;&#xD;
  &amp;lt;complex height="190"&amp;gt;&#xD;
    &amp;lt;child firstName="James" lastName="Bond"/&amp;gt;&#xD;
  &amp;lt;/complex&amp;gt;&#xD;
&amp;lt;/configuration&amp;gt;&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;这个配置的名字是complex，他有一个属性height，他的节点内还有一个child元素这个元素有两个属性firstName和lastName；对于这个内嵌的节点该如何实现呢？首先我们需要定义一个类，要从ConfigurationElement类继承，然后再用和SimpleSection类似的方法定义一些用ConfigurationProperty特性修饰的属性就可以了，当然属性值的get，set也要使用基类的索引器。如下实现：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;public class ComplexSection : ConfigurationSection&#xD;
{&#xD;
    [ConfigurationProperty("height", IsRequired = true)]&#xD;
    public int Height&#xD;
    {&#xD;
        get&#xD;
        {&#xD;
            return (int)base["height"];&#xD;
        }&#xD;
        set&#xD;
        {&#xD;
            base["height"] = value;&#xD;
        }&#xD;
    }&#xD;
&#xD;
    [ConfigurationProperty("child", IsDefaultCollection = false)]&#xD;
    public ChildSection Child&#xD;
    {&#xD;
        get&#xD;
        {&#xD;
            return (ChildSection)base["child"];&#xD;
        }&#xD;
        set&#xD;
        {&#xD;
            base["child"] = value;&#xD;
        }&#xD;
    }&#xD;
}&#xD;
&#xD;
public class ChildSection : ConfigurationElement&#xD;
{&#xD;
    [ConfigurationProperty("firstName", IsRequired = true, IsKey = true)]&#xD;
    public string FirstName&#xD;
    {&#xD;
        get&#xD;
        {&#xD;
            return (string)base["firstName"];&#xD;
        }&#xD;
        set&#xD;
        {&#xD;
            base["firstName"] = value;&#xD;
        }&#xD;
    }&#xD;
&#xD;
    [ConfigurationProperty("lastName", IsRequired = true)]&#xD;
    public string LastName&#xD;
    {&#xD;
        get&#xD;
        {&#xD;
            return (string)base["lastName"];&#xD;
        }&#xD;
        set&#xD;
        {&#xD;
            base["lastName"] = value;&#xD;
        }&#xD;
    }&#xD;
}&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;还有稍微再复杂一点的情况，我们可能要在配置中配置一组相同类型的节点，也就是一组节点的集合。如下面的配置：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;&amp;lt;?xml version="1.0" encoding="utf-8" ?&amp;gt;&#xD;
&amp;lt;configuration&amp;gt;&#xD;
  &amp;lt;configSections&amp;gt;&#xD;
    &amp;lt;section name="complex" type="ConfigExample.Configuration.ComplexSection,ConfigExample"/&amp;gt;&#xD;
  &amp;lt;/configSections&amp;gt;&#xD;
  &amp;lt;complex height="190"&amp;gt;&#xD;
    &amp;lt;child firstName="James" lastName="Bond"/&amp;gt;&#xD;
&#xD;
    &amp;lt;children&amp;gt;&#xD;
      &amp;lt;add firstName="Zhao" lastName="yukai"/&amp;gt;&#xD;
      &amp;lt;add firstName="Lee" lastName="yukai"/&amp;gt;&#xD;
      &amp;lt;remove firstName="Zhao"/&amp;gt;&#xD;
    &amp;lt;/children&amp;gt;&#xD;
  &amp;lt;/complex&amp;gt;&#xD;
&amp;lt;/configuration&amp;gt;&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;请看children节点，它就是一个集合类，在它里面定义了一组add元素，也可以有remove节点把已经添进去的配置去掉。&lt;/p&gt;&#xD;
&lt;p&gt;要使用自定义节点集合需要从ConfigurationElementCollection类继承一个自定义类，然后要实现此类GetElementKey(ConfigurationElement element)和ConfigurationElement CreateNewElement()两个方法；为了方便的访问子节点可以在这个类里面定义只读的索引器。请看下面的实现&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;public class Children : ConfigurationElementCollection&#xD;
{&#xD;
    protected override object GetElementKey(ConfigurationElement element)&#xD;
    {&#xD;
        return ((ChildSection)element).FirstName;&#xD;
    }&#xD;
&#xD;
    protected override ConfigurationElement CreateNewElement()&#xD;
    {&#xD;
        return new ChildSection();&#xD;
    }&#xD;
&#xD;
    public ChildSection this[int i]&#xD;
    {&#xD;
        get&#xD;
        {&#xD;
            return (ChildSection)base.BaseGet(i);&#xD;
        }&#xD;
    }&#xD;
&#xD;
    public ChildSection this[string key]&#xD;
    {&#xD;
        get&#xD;
        {&#xD;
            return (ChildSection)base.BaseGet(key);&#xD;
        }&#xD;
    }&#xD;
&#xD;
}&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;当然要使用此集合类我们必须在Complex类中添加一个此集合类的属性，并要指定集合类的元素类型等属性，如下：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;[ConfigurationProperty("children", IsDefaultCollection = false)]&#xD;
    [ConfigurationCollection(typeof(ChildSection), CollectionType = ConfigurationElementCollectionType.AddRemoveClearMap, RemoveItemName = "remove")]&#xD;
    public Children Children&#xD;
    {&#xD;
        get&#xD;
        {&#xD;
            return (Children)base["children"];&#xD;
        }&#xD;
        set&#xD;
        {&#xD;
            base["children"] = value;&#xD;
        }&#xD;
}&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;我们会经常用到类似appSettings配置节的键值对的构造，这时候我们就不必再自己实现了，我们可以直接使用现有的System.Configuration.NameValueConfigurationCollection类来定义一个自定义的键值对。可以在Complex类中定义如下属性&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;[ConfigurationProperty("NVs", IsDefaultCollection = false)]&#xD;
    public System.Configuration.NameValueConfigurationCollection NVs&#xD;
    {&#xD;
        get&#xD;
        {&#xD;
            return (NameValueConfigurationCollection)base["NVs"];&#xD;
        }&#xD;
        set&#xD;
        {&#xD;
            base["NVs"] = value;&#xD;
        }&#xD;
}&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;然后在配置文件的complex节中添加键值对配置&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;&amp;lt;NVs&amp;gt;&#xD;
    &amp;lt;add name="abc" value="123"/&amp;gt;&#xD;
    &amp;lt;add name="abcd" value="12d3"/&amp;gt;&#xD;
&amp;lt;/NVs&amp;gt;&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;到这儿已经基本上可以满足所有的配置需求了。不过还有一点更大但是不复杂的概念，就是sectionGroup。我们可以自定义SectionGroup，然后在sectionGroup中配置多个section；分组对于大的应用程序是很有意义的。&lt;/p&gt;&#xD;
&lt;p&gt;如下配置，配置了一个包含simple和一个complex两个section的sectionGroup&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;&amp;lt;?xml version="1.0" encoding="utf-8" ?&amp;gt;&#xD;
&amp;lt;configuration&amp;gt;&#xD;
  &amp;lt;configSections&amp;gt;&#xD;
    &amp;lt;sectionGroup type="ConfigExample.Configuration.SampleSectionGroup,ConfigExample" name="sampleGroup"&amp;gt;&#xD;
      &amp;lt;section type="ConfigExample.Configuration.SimpleSection,ConfigExample" allowDefinition="Everywhere" name="simple" /&amp;gt;&#xD;
      &amp;lt;section type="ConfigExample.Configuration.ComplexSection,ConfigExample" allowDefinition="Everywhere" name="complex"/&amp;gt;&#xD;
    &amp;lt;/sectionGroup&amp;gt;&#xD;
  &amp;lt;/configSections&amp;gt;&#xD;
  &amp;lt;sampleGroup&amp;gt;&#xD;
    &amp;lt;simple maxValue="20" minValue="1"&amp;gt;&#xD;
    &amp;lt;/simple&amp;gt;&#xD;
&#xD;
    &amp;lt;complex height="190"&amp;gt;&#xD;
      &amp;lt;child firstName="James" lastName="Bond"/&amp;gt;&#xD;
      &amp;lt;children&amp;gt;&#xD;
        &amp;lt;add firstName="Zhao" lastName="yukai"/&amp;gt;&#xD;
        &amp;lt;add firstName="Lee" lastName="yukai"/&amp;gt;&#xD;
        &amp;lt;remove firstName="Zhao"/&amp;gt;&#xD;
      &amp;lt;/children&amp;gt;&#xD;
  &amp;lt;NVs&amp;gt;&#xD;
    &amp;lt;add name="abc" value="123"/&amp;gt;&#xD;
    &amp;lt;add name="abcd" value="12d3"/&amp;gt;&#xD;
  &amp;lt;/NVs&amp;gt;&#xD;
    &amp;lt;/complex&amp;gt;&#xD;
  &amp;lt;/sampleGroup&amp;gt;&#xD;
&amp;lt;/configuration&amp;gt;&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;为了方便的存取sectionGroup中的section我们可以实现一个继承自System.Configuration.ConfigurationSectionGroup类的自定义类。实现很简单，就是通过基类的Sections[&amp;ldquo;sectionName&amp;rdquo;]索引器返回Section。如下：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;public class SampleSectionGroup : System.Configuration.ConfigurationSectionGroup&#xD;
{&#xD;
    public SimpleSection Simple&#xD;
    {&#xD;
        get&#xD;
        {&#xD;
            return (SimpleSection)base.Sections["simple"];&#xD;
        }&#xD;
    }&#xD;
&#xD;
    public ComplexSection Complex&#xD;
    {&#xD;
        get&#xD;
        {&#xD;
            return (ComplexSection)base.Sections["complex"];&#xD;
        }&#xD;
    }&#xD;
}&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;需要注意的是SectionGroup不能使用ConfigurationManager.GetSection(string)方法来获得，要获得sectionGroup必须通过Configuration类的SectionGroups[string]索引器获得，如下示例代码：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;SampleSectionGroup sample = (SampleSectionGroup)ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).SectionGroups["sampleGroup"];&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;总结：&lt;/p&gt;&#xD;
&lt;p&gt;.Net framework给我们提供了一套很方便的配置库，我们只需要继承对应的类简单的配置一下就可以方便的使用在web.config或者app.config中配置的自定义节点了。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a title="自定义配置源码" href="http://files.cnblogs.com/yukaizhao/ConfigExample.zip" target="_blank"&gt;自定义配置文件节源码&lt;/a&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yukaizhao/aggbug/2272422.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/12/02/net-web-config-costom-config-implement.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yukaizhao/archive/2011/12/01/monitor_wait_pulse.html</id><title type="text">Monitor的另一种用法</title><summary type="text">另一种用法可以在一个线程获得对象锁后再释放锁，然后阻塞当前线程执行，直到收到Monitor发出的信号后再继续执行</summary><published>2011-12-01T11:52:00Z</published><updated>2011-12-01T11:52:00Z</updated><author><name>玉开</name><uri>http://www.cnblogs.com/yukaizhao/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yukaizhao/archive/2011/12/01/monitor_wait_pulse.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yukaizhao/archive/2011/12/01/monitor_wait_pulse.html"/><content type="html">&lt;p&gt;在多线程编程时我们可以使用lock(obj){}代码块来控制关键代码的访问，lock其实是Monitor.Enter(obj)和Monitor.Exit(obj)的一种简单写法(&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2010/05/25/csharp-Syntactic-sugar.html" target="_blank"&gt;语法糖&lt;/a&gt;)。&lt;/p&gt;&#xD;
&lt;p&gt;这个随笔的标题是&amp;ldquo;Monitor的另一种用法&amp;rdquo;，所以我们不赘述lock了；我们进入主题，另一种用法可以在一个线程获得对象锁后再释放锁，然后阻塞当前线程执行，直到收到Monitor发出的信号后再继续执行。&lt;/p&gt;&#xD;
&lt;p&gt;这里有两件事一个是获得锁然后释放锁并阻塞当前线程，做这件事儿要用Monitor的Wait()方法&lt;/p&gt;&#xD;
&lt;p&gt;另一件事是线程获得锁，然后执行一段操作并发出信号让第一件事儿中被阻塞的线程继续执行，这需要使用Monitor的Pulse或者PulseAll方法。&lt;/p&gt;&#xD;
&lt;p&gt;请看下面的代码片段：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;class BlockingStack&amp;lt;T&amp;gt;&#xD;
    {&#xD;
        private readonly Stack&amp;lt;T&amp;gt; stack;&#xD;
        public BlockingStack(Stack&amp;lt;T&amp;gt; theStack)&#xD;
        {&#xD;
            this.stack = theStack;&#xD;
        }&#xD;
&#xD;
        public void Push(T item)&#xD;
        {&#xD;
            lock (stack)&#xD;
            {&#xD;
                stack.Push(item);&#xD;
                if (stack.Count == 1)&#xD;
                {&#xD;
                    //这儿发出信号，唤醒在Pop中阻塞的线程&#xD;
                    Monitor.PulseAll(stack);&#xD;
                }&#xD;
            }&#xD;
        }&#xD;
&#xD;
        public T Pop()&#xD;
        {&#xD;
            lock (stack)&#xD;
            {&#xD;
                if (stack.Count == 0)&#xD;
                {&#xD;
                    //栈是空的，所以等待直到收到信号为止&#xD;
                    Monitor.Wait(stack);&#xD;
                }&#xD;
                T item = stack.Pop();&#xD;
                return item;&#xD;
            }&#xD;
        }&#xD;
    }&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;总结： Monitor类还可以锁定对象后释放锁等待另外一个线程发出信号后继续执行。&lt;/p&gt;&#xD;
&lt;p&gt;参考：&lt;br /&gt;&lt;a href="http://www.codeproject.com/KB/IP/socketasynceventargssampl.aspx" target="_blank"&gt;http://www.codeproject.com/KB/IP/socketasynceventargssampl.aspx&lt;/a&gt;&lt;br /&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/system.threading.monitor.wait.aspx" target="_blank"&gt;http://msdn.microsoft.com/en-us/library/system.threading.monitor.wait.aspx&lt;/a&gt;&lt;br /&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/system.threading.monitor.pulse.aspx" target="_blank"&gt;http://msdn.microsoft.com/en-us/library/system.threading.monitor.pulse.aspx&lt;/a&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yukaizhao/aggbug/2271113.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/12/01/monitor_wait_pulse.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yukaizhao/archive/2011/11/30/use-gc-effective-3.html</id><title type="text">【译】让垃圾回收器高效工作（三）</title><summary type="text">还是垃圾回收的话题，这篇文章我们谈谈固定对象的内存地址（pinning）和弱引用对垃圾回收的影响和一些建议的最佳实践</summary><published>2011-11-30T00:50:00Z</published><updated>2011-11-30T00:50:00Z</updated><author><name>玉开</name><uri>http://www.cnblogs.com/yukaizhao/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yukaizhao/archive/2011/11/30/use-gc-effective-3.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yukaizhao/archive/2011/11/30/use-gc-effective-3.html"/><content type="html">&lt;p&gt;这篇文章我们谈谈固定对象的内存地址（pinning）和弱引用&amp;hellip;&amp;hellip;这两个和垃圾回收处理密切相关的东西。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;固定对象的内存地址：&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;固定对象的内存地址和实现Finalize方法的对象有一点是相同的 &amp;hellip;&amp;hellip; 两者都是因为我们的程序不得不和本地代码打交道。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;怎么固定对象的内存地址呢？有三种方法&lt;/strong&gt;&lt;br /&gt;1. 使用GCHandle的静态方法Alloc(object val,GCHandleType type) ，将type值设为GCHandleType.Pinned&lt;br /&gt;2. 在C#中使用fixed关键字&lt;br /&gt;3. 在调用本地代码时，本本地代码固定（例如：to marshal LPWSTR as a String object, Interop pins the buffer for the duration of the call）&lt;/p&gt;&#xD;
&lt;p&gt;对于小对象堆来说，在代码中固定对象地址是导致内存碎片的唯一原因，如果没有固定内存地址的对象那么在小对象堆中就不应该有碎片。&lt;/p&gt;&#xD;
&lt;p&gt;而对于大对象堆，固定内存地址的操作是无效的，因为现在的垃圾回收机制是不会移动大对象堆的对象的。不过，这一点只是GC的内部实现，你不应该依赖于这个实现，如果大对象需要固定内存地址，你还是要写固定需要的代码。&lt;/p&gt;&#xD;
&lt;p&gt;内存碎片从来都不是一个好东西。它会增加垃圾回收工作的难度 &amp;mdash;&amp;mdash; 如果没有固定内存地址的对象，垃圾回收器在移动内存时只需要将非垃圾对象覆盖空闲内存就可以了，而堆上存在固定地址的对象，垃圾回收器就不得不在移动中考虑，不覆盖这类对象，也不能移动它们。&lt;/p&gt;&#xD;
&lt;p&gt;那么如何才能知道你的程序中有多少内存碎片呢？你可以使用!dumpheap命令：&amp;ldquo;dumpheap &amp;ndash;type Free -stat&amp;rdquo;会给出所有释放对象占用内存的统计信息。 通常情况下如果碎片大小占总大小的比例小于10%的话，就没什么可担忧的。因此如果你看到释放对象的绝对数很大，但是总数少于10%就没必要害怕。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;如果你确实需要固定对象的地址，请注意下面几件事情：&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;1. 短时间的固定开销会很小&lt;/strong&gt;&lt;br /&gt;&amp;ldquo;短时间&amp;rdquo;，多短算短呢？ 如果固定内存地址的对象在垃圾回收之前就成为垃圾对象了，那么这个时间就是足够短了。因为固定内存其实就是在对象头置一个bit位，如果在对象存活期没有发生垃圾回收，那么就没有额外开销。如果在垃圾回收发生后这个对象还活着，垃圾回收器在移动内存时就得做更多的计算保证不会移动此对象，也不会覆盖它。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;2. 固定老对象的代价会比固定年轻对象的代价要小一些&lt;/strong&gt;&lt;br /&gt;何为&amp;ldquo;老对象&amp;rdquo;呢？是指经过两次垃圾回收，已经被迁移到2代堆的对象；这时候对象的所在的内存区域已经相对稳定了。造成内存碎片的可能性会小一些。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;br /&gt;&lt;strong&gt;两个固定对象内存地址好实践：&lt;/strong&gt;&lt;br /&gt;1. 在大对象堆上分配固定地址的对象，每次使用使使用其中的一部分&lt;br /&gt;这样做的优点是显而易见的，大对象堆不会做内存移动操作，所以就不存在因为固定对象地址导致的开销了；缺点是没有现成的API来把大对象分成一小块一小块使用，这需要开发人员按需编码使用。&lt;/p&gt;&#xD;
&lt;p&gt;2. 分配一个小对象的缓冲池，（and then hand them out when needed）&lt;br /&gt;例如，我有一个缓冲池，方法M有一个byte[]数组需要固定内存地址。如果这个数组已经是2代对象了，就固定它。而如果缓冲区不需要使用很长时间，那么就在0代和1代回收时回收它。这样所有在缓冲池中的对象就都是2代对象了。&lt;br /&gt;void M(byte[] b)&lt;br /&gt;{&lt;br /&gt; if (GC.GetGeneration(b) == GC.MaxGeneration)&lt;br /&gt; {&lt;br /&gt; RealM(b);&lt;br /&gt; return;&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; // GetBuffer will allocate one if no buffers&lt;br /&gt; // are available in the buffer pool.&lt;br /&gt; byte[] TempBuffer = BufferPool.GetBuffer();&lt;br /&gt; RealM(TempBuffer);&lt;br /&gt; CopyBackToUserBuffer(TempBuffer, b);&lt;br /&gt; BufferPool.Release(TempBuffer);&lt;br /&gt;}&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;弱引用：&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;弱引用是如何实现的呢？&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;一个弱引用对象有托管和非托管两个部分。托管部分是WeakReference对象本身。在它的构造函数中我们创建一个GC句柄，它是非托管的部分 &amp;mdash;&amp;mdash; 这会在AppDomain的句柄表中插入一项（GCHandle类的Alloc方法都是这么做的，只不过是将不同类型插入到各自的表中）。当弱引用指向的对象没有强引用时就会被垃圾回收器回收掉。因为WeakReference对象本身是一个托管对象，所以它没有强引用时也会被回收。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;弱引用没必要引用小对象：&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;如果你有一个非常小的对象，比如说一个DWORD字段，对象的大小是12byte（12byte是对象的最小尺寸）。而WeakReference对象有一个IntPtr和一个bool字段，而GC句柄是一个指针的大小（32位机器4byte，64位机器8byte）；这就是说你需要使用15个byte的对象来延长一个12byte对象的长度，这是不划算的。显然你不应该创建很多弱引用对象来引用一些小对象。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;那么，弱引用有什么用呢？&lt;/strong&gt;&lt;br /&gt;弱引用的作用是在垃圾回收发生之前即便对象上没有强引用，也可以再次使用该对象。如果执行垃圾回收它会被回收。&lt;/p&gt;&#xD;
&lt;p&gt;为什么要使用弱引用来跟踪一个对象的释放，而不是用Finalizer方法呢？ 使用弱引用的优点是跟踪的对象不会被推迟到下次垃圾回收时才真正的被回收；缺点是需要消耗一点内存，只有当用户代码检查弱引用指向的对象为null时才清除对象。&lt;/p&gt;&#xD;
&lt;p&gt;下面是两个弱对象的使用实例：&lt;br /&gt;Option A):&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;class A&#xD;
{&#xD;
  WeakReference _target; &#xD;
&#xD;
  MyObject Target&#xD;
  {&#xD;
     set&#xD;
    {&#xD;
       _target = new WeakReference(value);&#xD;
    }&#xD;
     get&#xD;
    {&#xD;
       Object o = _target.Target;&#xD;
       if (o != null)&#xD;
       {&#xD;
           return o;&#xD;
       }&#xD;
       else&#xD;
       {&#xD;
          // my target has been GC'd - clean up&#xD;
          Cleanup(); &#xD;
          return null;&#xD;
       }&#xD;
    }&#xD;
}&#xD;
&#xD;
void M()&#xD;
{&#xD;
   // target needs to be alive throughout this method.&#xD;
  MyObject target = Target;&#xD;
&#xD;
   if (target == null)&#xD;
   // target has been GC'd, don't bother&#xD;
   return;&#xD;
   else&#xD;
   {&#xD;
      // always need target to be alive.&#xD;
      DoSomeWork();&#xD;
   }&#xD;
&#xD;
    GC.KeepAlive(target);&#xD;
}&#xD;
}&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;Option B):&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;class A&#xD;
{&#xD;
    WeakReference _target;&#xD;
    MyObject ShortTemp;&#xD;
 &#xD;
    MyObject Target&#xD;
    {&#xD;
        set&#xD;
        {&#xD;
            _target = new WeakReference(value);&#xD;
        }&#xD;
 &#xD;
        get&#xD;
        {&#xD;
            Object o = _target.Target;&#xD;
            if (o != null)&#xD;
            {&#xD;
                return o;&#xD;
            }&#xD;
            else&#xD;
            {&#xD;
                // my target has been GC'd - clean up&#xD;
                Cleanup();       &#xD;
                return null;&#xD;
            }&#xD;
        }&#xD;
    }&#xD;
 &#xD;
    void M()&#xD;
    {&#xD;
        // target needs to be alive throughout this method.&#xD;
        MyObject target = Target;&#xD;
        ShortTemp = target;&#xD;
 &#xD;
        if (target == null)&#xD;
        {&#xD;
            // target has been GC'd, don't bother&#xD;
            return;&#xD;
        }&#xD;
        else&#xD;
        {&#xD;
            // could assert that ShortTemp is not null.&#xD;
            DoSomeWork();&#xD;
        }&#xD;
 &#xD;
        ShortTemp = null;&#xD;
    }&#xD;
}&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;使用弱对象维护缓存:&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;你可以创建一个WeakReference指向对象的数组&lt;br /&gt;WeakReferencesToObjects WeakRefs[];&lt;/p&gt;&#xD;
&lt;p&gt;数组中的每个元素都是弱对象指向的对象。我们可以定期的遍历这个数组看哪个对象已经死了然后释放引用此对象的WeakReference对象。&lt;/p&gt;&#xD;
&lt;p&gt;如果我们经常处理已经释放的对象，缓存就会在每次垃圾回收之后失效。&lt;/p&gt;&#xD;
&lt;p&gt;如果对你来说这还不够，你可以使用二级缓存机制。&lt;/p&gt;&#xD;
&lt;p&gt;维持一段时间的缓存项的强引用列表，在这段时间之后，将强引用转换成弱引用，弱引用意味着这些项将要被剔除。&lt;/p&gt;&#xD;
&lt;p&gt;你可能有不同的策略来处理缓存，比如根据缓存被读取次数&amp;mdash;&amp;mdash;读取次数少的项被转换成弱引用；或者根据缓存项的个数，如果缓存项数超过某个数字就把超过的缓存项设置为弱引用。这取决于你的应用。调优缓存是另一个完整的主题&amp;mdash;&amp;mdash;或许将来我会写一下。&lt;/p&gt;&#xD;
&lt;p&gt;原文：http://blogs.msdn.com/b/maoni/archive/2004/12/19/327149.aspx&lt;br /&gt;原作者：Maoni Stephens&lt;/p&gt;&#xD;
&lt;p&gt;相关随笔：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/29/use-gc-effective-2.html"&gt;让垃圾回收器高效工作（二）&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/28/use-gc-effective-1.html"&gt;让垃圾回收器高效工作（一）&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/23/dot_net_GC_1.html"&gt;.Net 垃圾回收机制原理（一）&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/25/dot_net_GC_2.html"&gt;.Net 垃圾回收机制原理（二）&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/21/dot_net_gc_large_object_heap.html"&gt;.Net 垃圾回收和大对象处理&lt;/a&gt;&amp;nbsp;&lt;/p&gt;&#xD;
&lt;p&gt;参考：&lt;br /&gt;使用GCHandle固定内存&lt;br /&gt;&lt;a href="http://www.codeproject.com/KB/cs/PinnedObject.aspx" target="_blank"&gt;http://www.codeproject.com/KB/cs/PinnedObject.aspx&lt;/a&gt; &lt;br /&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/83y4ak54.aspx" target="_blank"&gt;http://msdn.microsoft.com/en-us/library/83y4ak54.aspx&lt;/a&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yukaizhao/aggbug/2268543.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/30/use-gc-effective-3.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yukaizhao/archive/2011/11/29/use-gc-effective-2.html</id><title type="text">【译】让垃圾回收器高效工作（二）</title><summary type="text">这篇文章我们谈谈GC的不同工作模式，以及各个模式如何工作和他们之间的不同，让你明白你的应用程序该如何选择工作模式</summary><published>2011-11-29T00:42:00Z</published><updated>2011-11-29T00:42:00Z</updated><author><name>玉开</name><uri>http://www.cnblogs.com/yukaizhao/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yukaizhao/archive/2011/11/29/use-gc-effective-2.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yukaizhao/archive/2011/11/29/use-gc-effective-2.html"/><content type="html">&lt;p&gt;这篇文章我们谈谈GC的不同工作模式，以及各个模式如何工作和他们之间的不同，让你明白你的应用程序该如何选择工作模式。&lt;/p&gt;&#xD;
&lt;p&gt;迄今为止运行时GC工作模式：&lt;/p&gt;&#xD;
&lt;p&gt;1）关闭并发的工作站GC&lt;br /&gt;2）开启并发的工作站GC&lt;br /&gt;3）服务器GC&lt;/p&gt;&#xD;
&lt;p&gt;如果你在写一个独立的托管程序并且没有做任何配置，你使用的GC工作模式是开启并发的工作站GC。这一点多数人可能会感到惊讶，因为我们的文档中并没有提起并发GC，有时候会把并发GC称为&amp;rdquo;background GC&amp;rdquo;（while referring working GC as &amp;ldquo;foreground GC&amp;rdquo;）.&lt;/p&gt;&#xD;
&lt;p&gt;如果你的程序在宿主程序中运行，宿主可能会为程序选择GC的工作模式。&lt;/p&gt;&#xD;
&lt;p&gt;需要注意的是：如果你的程序配置成服务器GC，你的程序运行在一台高性能的机器上，实际上你的服务器是运行在&amp;ldquo;关闭并发的工作GC&amp;rdquo;模式下。因为&amp;ldquo;关闭并发的工作站GC&amp;rdquo;为高性能服务器的大吞吐量服务做了优化。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;各个GC模式的设计目标：&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;1） 关闭并发的工作站GC为高性能服务器的高吞吐量做了优化。我们在垃圾回收时根据分配和复活模式做动态调优因此可以程序运行时自动调优GC的工作效率。&lt;br /&gt;2） 开启并发的工作站GC是为要求精确响应时间的交互式应用程序设计的。开启并发使垃圾回收造成的工作进程暂停时间缩短。 这个目的是用一些内存和CPU换来的，因此在这种模式下垃圾回收需要做的工作略多一点需要的回收时间会略长一些。&lt;br /&gt;3） 服务器GC，从名字上我们可以看出这种工作模式是为服务器应用程序设计的；典型的场景是你有一个工作线程池这些线程坐着相似的处理。例如：做处理同样的请求或者处理相同类型的事务。所有的线程使用几乎相同的分配模式。服务器GC是为要求高吞吐量的和高扩展性的多处理器服务器设计的&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;各个GC模式如何工作：&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;让我们从关闭并发的工作站GC说起，其执行流程如下：&lt;br /&gt;1） 一个托管进程做内存分配&lt;br /&gt;2） 分配完所有可用的内存（我会解释这是什么意思）&lt;br /&gt;3） 触发了垃圾回收，垃圾回收操作在做分配的线程上运行&lt;br /&gt;4） GC调用SuspendEE来挂起所有的托管线程&lt;br /&gt;5） GC开始工作&lt;br /&gt;6） GC调用RestartEE来重启工作托管进程&lt;br /&gt;7） 托管进程继续运行&lt;/p&gt;&#xD;
&lt;p&gt;你可以看到在第5步中所有的托管线程都停止执行来等待垃圾回收完成工作。SuspendEE不会挂起本地线程（native threads）。&lt;br /&gt;GC中的每一代都有一个&amp;ldquo;分配预算&amp;rdquo;的概念。每一代的&amp;ldquo;分配预算&amp;rdquo;在运行时是动态调整的。因为我们经常在0代上做分配，你可以想象0代预算超支了，这样就会触发垃圾回收。这里的预算和GC堆的段大小完全不是一回事，预算比段大小要小得多。&lt;/p&gt;&#xD;
&lt;p&gt;CLR垃圾回收可以做内存移动也可以不做移动。不做移动时也称为&amp;ldquo;清扫&amp;rdquo;，清扫的代价要比做压缩的代价低一些，因为他不需要复制移动内存。&lt;/p&gt;&#xD;
&lt;p&gt;在开启并发垃圾回收（Concurrent GC）时，最大的差异是挂起和重启。 我前面提到并发GC允许更少的暂停时间。因此开启并发的垃圾回收会尽可能少的执行垃圾回收，执行时间也非常短。在剩余的时间中如果需要托管线程可以运行和分配内存。开启并发时我们会在开始时给0代一个很大的分配预算来保证垃圾回收运行期间有足够的空间分配对象。尽管如此，如果在并发回收运行中托管线程需要分配过多的内存，线程也会被堵塞直到回收完成。&lt;/p&gt;&#xD;
&lt;p&gt;记住0代和1代回收非常快，所以在做0，1代回收时是不会做并发回收的。我们只是在2代回收时才会并发回收。如果我们决定做2代回收，我们会决定是否并发回收。&lt;/p&gt;&#xD;
&lt;p&gt;服务器垃圾回收，这种模式和前两种完全不同。我们会为每一个CPU创建一个回收线程。垃圾回收在这些线程上执行而不是在分配线程上，其工作流程如下：&lt;br /&gt;1. 一个托管线程做回收&lt;br /&gt;2. 分配达到阀值&lt;br /&gt;3. 给GC线程发信号，让GC线程做垃圾回收，等待回收结束&lt;br /&gt;4. GC线程运行，结束时发出回收完成的信号（在回收过程中，所有的托管线程会像工作站模式中一样被挂起）&lt;br /&gt;5. 托管线程收到信号重新开始运行&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;如果配置各个垃圾回收模式：&lt;/strong&gt;&lt;br /&gt;要关闭并发回收，在配置文件中添加下面配置项：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;&amp;lt;configuration&amp;gt;&#xD;
&amp;lt;runtime&amp;gt;&#xD;
&amp;lt;gcConcurrent enabled="false"/&amp;gt;&#xD;
&amp;lt;/runtime&amp;gt;&#xD;
&amp;lt;/configuration&amp;gt;&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;要使用服务器GC，使用下面配置：&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;&amp;lt;configuration&amp;gt;&#xD;
&amp;lt;runtime&amp;gt;&#xD;
&amp;lt;gcServer enabled=&amp;ldquo;true"/&amp;gt;&#xD;
&amp;lt;/runtime&amp;gt;&#xD;
&amp;lt;/configuration&amp;gt;&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;关于这两个配置节可以参考msdn。&lt;/p&gt;&#xD;
&lt;p&gt;原文：&lt;br /&gt;&lt;a href="http://blogs.msdn.com/b/maoni/archive/2004/09/25/234273.aspx" target="_blank"&gt;http://blogs.msdn.com/b/maoni/archive/2004/09/25/234273.aspx&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;原作者：Maoni Stephens&lt;/p&gt;&#xD;
&lt;p&gt;相关随笔：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/28/use-gc-effective-1.html"&gt;让垃圾回收器高效工作（一）&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/23/dot_net_GC_1.html"&gt;.Net 垃圾回收机制原理（一）&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/25/dot_net_GC_2.html"&gt;.Net 垃圾回收机制原理（二）&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/21/dot_net_gc_large_object_heap.html"&gt;.Net 垃圾回收和大对象处理&lt;/a&gt;&amp;nbsp;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;br /&gt;更多参考资源：&lt;br /&gt;http://blogs.msdn.com/b/tess/archive/2008/04/17/how-does-the-gc-work-and-what-are-the-sizes-of-the-different-generations.aspx &lt;br /&gt;http://odetocode.com/Blogs/scott/archive/2004/07/16/server-or-workstation-garbage-collection.aspx&lt;br /&gt;http://msdn.microsoft.com/en-us/library/yhwwzef8.aspx&lt;br /&gt;http://msdn.microsoft.com/en-us/library/ms229357.aspx&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yukaizhao/aggbug/2266988.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/29/use-gc-effective-2.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yukaizhao/archive/2011/11/28/use-gc-effective-1.html</id><title type="text">【译】让垃圾回收器高效工作（一）</title><summary type="text">本文首先介绍了分配对象回收对象都有哪些开销，然后介绍了从垃圾回收角度该如何组织数据，该用引用类型还是值类型，对象的存储位置对垃圾回收有什么影响，如何使用大对象才是有效的等等</summary><published>2011-11-28T00:42:00Z</published><updated>2011-11-28T00:42:00Z</updated><author><name>玉开</name><uri>http://www.cnblogs.com/yukaizhao/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yukaizhao/archive/2011/11/28/use-gc-effective-1.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yukaizhao/archive/2011/11/28/use-gc-effective-1.html"/><content type="html">&lt;p&gt;&lt;strong&gt;如何提高垃圾回收的工作效率&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;这篇文章的目的是介绍如何更有效率的使用托管内存，而不是介绍GC本身的，是解释如何使用GC。我假定多数读者都对如何用好GC感兴趣，而不是自己实现一个GC。这篇文章需要对GC有一个基本的了解，Jeff Richter写了两篇很棒的文章介绍GC，我翻译了这两篇文章，如果你尚对GC一点都不了解，建议先看下垃圾回收原理&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/23/dot_net_GC_1.html" target="_blank"&gt;1&lt;/a&gt;和&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/25/dot_net_GC_2.html"&gt;2&lt;/a&gt;.&lt;/p&gt;&#xD;
&lt;p&gt;首先我会专注于Workstation GC（因此所有的数字都是工作站GC的）。然后我会谈谈工作站GC和服务器GC之间的区别（有时候你没有必要选择，稍后我会解释为什么）。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;代：&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;把托管堆上的对象分成3代是为了调优垃圾回收的性能，大多数对象都在0代时消亡。例如：在一个服务器程序中，处理每个请求相关的对象，都会在请求完成后消亡。本质上1代对象是在新分配对象和常驻内存之间的一个缓冲区。当你在性能计数器中观察2代回收发生的次数比0代回收次数要少的多。而1代回收次数相对来说不是很重要，回收1代对象比回收0代对象的代价高的不是很多。而回收2代对象就意味着要扫描整个托管堆了，代价相对要大得多。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;GC段（segment）：&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;首先让我们看一下GC是如何向操作系统申请内存的。GC以段的方式保留内存。每一个段是16M（服务器模式下可能是64M）。当执行引擎启动时，我们保留初始的GC段，一个给小对象用，另一个段给大对象用。有关大对象堆的垃圾回收请&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/21/dot_net_gc_large_object_heap.html" target="_blank"&gt;参考这里&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;在需要的时候可以向操作系统申请更多内存，或者交还给操作系统。当所有段都用完之后我们就申请一个新段。在每一次完整的垃圾回收之后多余的段会交还给操作系统。&lt;/p&gt;&#xD;
&lt;p&gt;大对象有自己的段，垃圾回收器对大对象的处理方式和小对象是不一样的所以大对象不和小对象共享段。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;分配：&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;当你在托管堆上分配一个对象时，要付出什么代价呢？如果我们不考虑回收的话，有两点1是向前移动指针，2是为新对象清空内存。而对于实现Finalize的方法的对象还需要把对象的指针放到终结队列中。&lt;/p&gt;&#xD;
&lt;p&gt;注意我说的是&amp;ldquo;如果我们不考虑回收&amp;rdquo;&amp;mdash;这意味着分配的代价和对象的大小成正比。申请的越少，GC的代价就越小。如果你需要15个byte，就申请15个字节；不要像使用maalloc一样申请32个字节。有一个阀值，当超过这个值时，就会触发垃圾回收。你要尽可能少的触发垃圾回收。&lt;/p&gt;&#xD;
&lt;p&gt;GC堆和NT堆还有一点不同：分配对象的时间越接近，对象在GC堆上的也越接近。&lt;/p&gt;&#xD;
&lt;p&gt;在GC堆上分配的每一个对象都需要额外的8byte的开销，4byte用来同步，4byte存放方法表指针。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;回收：&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;首先我们要知道什么时候触发回收？ 有如下三种情况会触发：&lt;br /&gt;1. 分配时超过了0代堆的阀值&lt;br /&gt;2. 调用了GC.Collect()方法&lt;br /&gt;3. 操作系统给应用程序发出低内存信号&lt;/p&gt;&#xD;
&lt;p&gt;第1种情况是最典型的触发原因，当分配的对象足够多时，就会触发0代堆的垃圾回收。在每一次回收之后，0代堆就清空了。然后继续分配对象，0代堆填满之后就会触发下一次回收。&lt;/p&gt;&#xD;
&lt;p&gt;你要尽量避免第2种情况，这个很简单，不要在程序代码中调用GC.Collect方法就可以了。通常情况下你不应该调用Collect方法。BCL is basically the only place that should call this (in very limited places)；当你在程序中调用GC.Collect方法时，性能会降低，因为回收提前执行了，而垃圾回收器执行回收的调度是经过算法优化的。&lt;/p&gt;&#xD;
&lt;p&gt;第3种情况受操作系统上运行的其他程序影响，这个你的程序没法控制，你只能尽可能的优化好你的程序或模块。&lt;/p&gt;&#xD;
&lt;p&gt;让我们谈一下这意味着什么。首先，托管堆是程序的工作集的一部分。它会消耗私有页。在理想情况下，所有对象都在0代时消亡（这意味着，几乎所有对象都在0代回收，完全回收从不会发生）因此，你的GC堆永远不会超过0代堆的大小。而事实上这种情况是不可能的，因此，你真的需要保证托管堆的大小是可控的。&lt;/p&gt;&#xD;
&lt;p&gt;第二，你需要保证垃圾回收消耗的时间资源是可控的。这个意思是一要尽可能少触发GC，二尽可能少发生高代的GC。一次高代的回收要比底一代回收的代价高得多，因为高代的回收要扫描更多的对象，要同时执行所有更低代的回收。&lt;/p&gt;&#xD;
&lt;p&gt;CLRProfiler是一个观察GC堆看堆上的对象被那个对象引用的工具，它非常棒。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;如何组织你的数据：&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;1） 用值类型还是引用类型&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;如你所知，值类型数据是存放在栈上的，而引用类型对象是存在托管堆上的。因此，人们会问，如何决定什么时候使用值类型，什么使用引用类型呢。值类型不会触发垃圾回收，但是如果你的值类型经常做装箱操作，装箱操作要比刚开始就创建一个引用类型对象要昂贵的多；当值类型对象作为参数传递时需要复制一份。但是如果你的引用类型只有一个小成员如果做成引用类型的话，还需要额外4字节的指针开销和同步开销以及方法表开销。因此该使用值类型还是引用类型是由类型本身决定的。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;2） 富引用对象（Reference rich object）&lt;/strong&gt;&lt;br /&gt;如果一个对象是富引用的，会给分配和回收都带拉压力。每一个内嵌的对象都需要8字节的额外开销。因为分配的开销和对象的大小是成正比的，所以开销就大了一些。另外富引用会导致构建对象图的时间增大，增加了回收的开销。&lt;/p&gt;&#xD;
&lt;p&gt;因此我建议你设计对象时只设计必要的字段，如果对另外一个引用类型的强引用不是必须的，就不要引用它。你应该尽量避免让已存在很长时间的对象引用新分配的对象。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;3） 可终结对象（实现Finalize方法的对象）&lt;/strong&gt;&lt;br /&gt;如垃圾回收原理1中所述终结对象会延长回收的时间，不仅延长可终结对象本身的，还会延长它的引用链下游的所有对象的回收时间。所以如果对象必须是可终结的，你就要尽可能的隔离它，不让它引用其他对象&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;4） 对象的存储位置：&lt;/strong&gt;&lt;br /&gt;当你为一个对象的子对象分配空间时，你最好在同一时间分配父对象和子对象，这样父子对象在托管堆上的地址就会在一起，回收起来也会一起回收，回收的效率就会相对高一些。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;大对象：&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;当一个对象占用的内存超过85,000bytes时它就会被分配到LOH上。SOH段永远都不会做移动&amp;mdash;而只是清空对象（使用一个空的链表）。但是这个情况是一种实现的细节，你不应该依赖这个实现细节。如果你分配了一个大对象，不希望他发生移动，那么你应该fix它。&lt;/p&gt;&#xD;
&lt;p&gt;只有在2代回收时才会做大对象的回收，而2代回收的代价是很大的。有时候你会发现2代回收之后2代堆的大小并没有发生多大变化，这有可能是因为大对象堆大小超过阀值触发了2代回收。&lt;/p&gt;&#xD;
&lt;p&gt;一个好的实践：分配一个大对象然后重复利用它。如果说你需要一个100k或者120k的大对象，你应该申请一个120k的然后重复利用它。多次分配临时大对象可能会触发2代回收，对性能会有负面影响。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;br /&gt;此文是翻译文章：&lt;br /&gt;原文地址：&lt;a href="http://blogs.msdn.com/b/maoni/archive/2004/06/15/156626.aspx" target="_blank"&gt;http://blogs.msdn.com/b/maoni/archive/2004/06/15/156626.aspx&lt;/a&gt;&lt;br /&gt;原作者：Maoni Stephens&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;相关随笔：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/23/dot_net_GC_1.html"&gt;.Net 垃圾回收机制原理（一）&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/25/dot_net_GC_2.html"&gt;.Net 垃圾回收机制原理（二）&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/21/dot_net_gc_large_object_heap.html"&gt;.Net 垃圾回收和大对象处理&lt;/a&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yukaizhao/aggbug/2265732.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/28/use-gc-effective-1.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yukaizhao/archive/2011/11/25/dot_net_GC_2.html</id><title type="text">【译】.Net 垃圾回收机制原理（二）</title><summary type="text">垃圾回收是.Net程序管理托管内存的基础，这篇我们看下弱引用对象，代，多线程垃圾回收，大对象处理以及和垃圾回收相关的性能计数器</summary><published>2011-11-25T00:52:00Z</published><updated>2011-11-25T00:52:00Z</updated><author><name>玉开</name><uri>http://www.cnblogs.com/yukaizhao/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yukaizhao/archive/2011/11/25/dot_net_GC_2.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yukaizhao/archive/2011/11/25/dot_net_GC_2.html"/><content type="html">&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/23/dot_net_GC_1.html" target="_blank"&gt;上一篇文章&lt;/a&gt;介绍了.Net 垃圾回收的基本原理和垃圾回收执行Finalize方法的内部机制；这一篇我们看下弱引用对象，代，多线程垃圾回收，大对象处理以及和垃圾回收相关的性能计数器。&lt;/p&gt;&#xD;
&lt;p&gt;让我们从弱引用对象说起，弱引用对象可以减轻大对象带来的内存压力。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;弱引用（Weak References）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;当程序的根对象指向一个对象时，这个对象是可达的，垃圾回收器不能回收它，这称为对对象的强引用。和强引用相对的是弱引用，当一个对象上存在弱引用时，垃圾回收器可以回收此对象，但是也允许程序访问这个对象。这是怎么回事儿呢？请往下看。&lt;/p&gt;&#xD;
&lt;p&gt;如果一个对象上仅存在弱引用，并且垃圾回收器在运行，这个对象就会被回收，之后如果程序中要访问这个对象，访问就会失败。另一方面，要使用弱引用的对象，程序必须先对这个对象进行强引用，如果程序在垃圾回收器回收这个对象之前对对象进行了强引用，这样（有了强引用之后）垃圾回收器就不能回收此对象了。这有点绕，让我们用一段代码来说明一下：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;void Method() {&#xD;
//创建对象的强引用&#xD;
Object o = new Object(); &#xD;
// 用一个短弱引用对象弱引用o.&#xD;
WeakReference wr = new WeakReference(o);&#xD;
&#xD;
o = null; // 移除对象的强引用&#xD;
&#xD;
o = wr.Target; //尝试从弱引用对象中获得对象的强引用&#xD;
if (o == null) {&#xD;
// 如果对象为空说明对象已经被垃圾回收器回收掉了&#xD;
} else {&#xD;
// 如果垃圾回收器还没有回收此对象就可以继续使用对象了&#xD;
}&#xD;
}&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;为什么需要弱对象呢？因为，有一些数据创建起来很容易，但是却需要很多内存。例如：你有一个程序，这个程序需要访问用户硬盘上的所有文件夹和文件名；你可以在程序第一次需要这个数据时访问用户磁盘生成一次数据，数据生成之后你就可以访问内存中的数据来得到用户文件数据，而不是每次都去读磁盘获得数据，这样做可以提升程序的性能。&lt;/p&gt;&#xD;
&lt;p&gt;问题是这个数据可能相当大，需要相当大的内存。如果用户去操作程序的另外一部分功能了，这块相当大的内存就没有占用的必要了。你可以通过代码删除这些数据，但是如果用户马上切换到需要这块数据的功能上，你就必须重新从用户的磁盘上构建这个数据。弱引用为这种场景提供了一种简单有效的方案。&lt;/p&gt;&#xD;
&lt;p&gt;当用户切换到其他功能时，你可以为这个数据创建一个弱引用对象，并把对这个数据的强引用解除掉。这样如果程序占用的内存很低，垃圾回收操作就不会触发，弱引用对象就不会被回收掉；这样当程序需要使用这块数据时就可以通过一个强引用来获得数据，如果成功得到了对象引用，程序就没有必要再次读取用户的磁盘了。&lt;/p&gt;&#xD;
&lt;p&gt;WeakReference类型提供了两个构造函数：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;WeakReference(object target);&#xD;
WeakReference(object target, bool trackResurrection);&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;target参数显然就是弱引用要跟踪的对象了。trackResurrection参数表示当对象的Finalize方法执行之后是否还要跟踪这个对象。默认这个参数是false。有关对象的复活请参考&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/23/dot_net_GC_1.html" target="_blank"&gt;这里&lt;/a&gt;。&lt;/p&gt;&#xD;
&lt;p&gt;方便起见，不跟踪复活对象的弱引用称为&amp;ldquo;短弱引用&amp;rdquo;；而要跟踪复活对象的的弱引用称为&amp;ldquo;长弱引用&amp;rdquo;。如果对象没有实现Finalize方法，那么长弱引用和短弱引用是完全一样的。强烈建议你尽量避免使用长弱引用。长弱引用允许你使用复活的对象，而复活对象的行为可能是不可以预知的。&lt;/p&gt;&#xD;
&lt;p&gt;一旦你使用WeakReference引用了一个对象，建议你将这个对象的所有强用都设置为null；如果强引用存在的话，垃圾回收器是永远都不可能回收弱引用指向的对象的。&lt;/p&gt;&#xD;
&lt;p&gt;当你要使用弱引用目标对象时，你必须为目标对象创建一个强引用，这很简单，只要用object a = weekRefer.Target;就可以了，然后你必须判断a是否为空，弱不为空才可以继续使用，弱为空就表示对象已经被垃圾回收器回收了，得通过其他方法重新获得此对象。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;弱引用的内部实现&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;从前文中的描述中我们可以推断出弱引用对象肯定和一般对象的处理是不一样的。一般情况下如果一个对象引用了另一个对象就是强引用，垃圾回收器就不能回收被引用的对象，而WeakReference对象却不是这样子，它引用的对象是有可能被回收的。&lt;/p&gt;&#xD;
&lt;p&gt;要完全理解弱对象是如何工作的，我们还需要看一下托管堆。托管堆上有两个内部数据结构他们的唯一作用是管理弱引用：我们可以把它们称作长弱引用表和短弱引用表；这两个表存放托管堆上的弱引用目标对象指针。&lt;/p&gt;&#xD;
&lt;p&gt;程序运行之初，这两个表都是空的。当你创建一个WeakReference对象时，这个对象并不是分配到托管堆上的，而是在弱对象表中创建一个空槽（Empty Slot）。短弱引用对象被放在短弱对象表中，长弱引用对象被放在长弱引用表中。&lt;/p&gt;&#xD;
&lt;p&gt;一旦发现空槽，空槽的值会被设置成弱引用目标对象的地址；显然长短弱对象表中的对象是不会当作应用程序的根对象的。垃圾回收器不会回收长短弱对象表中的数据。&lt;/p&gt;&#xD;
&lt;p&gt;让我们来看下垃圾回收执行时发生了什么：&lt;br /&gt;1. 垃圾回收器构建一个可达对象图，构建步骤请&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/23/dot_net_GC_1.html" target="_blank"&gt;参考上文&lt;/a&gt;&lt;br /&gt;2. 垃圾回收器扫描短弱对象表，如果弱对象表中指向的对像没有在可达对象图中，那么这个对像就被标识为垃圾对象，然后短对象表中的对象指针被设置为空&lt;br /&gt;3. 垃圾回收器扫描终结队列（&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/23/dot_net_GC_1.html" target="_blank"&gt;参考上文&lt;/a&gt;），如果队列中的对象不在可达对象图中，这个对象从终结队列中移动到Freachable队列中，这时候，这个对象又被标识为可达对象，不再是垃圾了&lt;br /&gt;4. 垃圾回收器扫描长弱引用表。如果表中的对象不在可达对象图中（可达对象图中包括在Freachable队列中对象），将长引用对象表中对应的对象指针设置为null&lt;br /&gt;5. 垃圾回收器移动可达对象&lt;/p&gt;&#xD;
&lt;p&gt;一旦你理解了垃圾回收器的工作过程，就很容易理解弱引用是如何起作用了。访问WeakReference的Target属性导致系统返回弱对象表中的目标对象指针，如果是null，表示对象已经被回收了。&lt;/p&gt;&#xD;
&lt;p&gt;短弱引用不跟踪复活，这意味着垃圾回收器可以在扫描终结队列之前检查弱引用表中指向的对象是否是垃圾对象。&lt;/p&gt;&#xD;
&lt;p&gt;而长弱引用跟踪复活对象，这意味着垃圾回收器必须在确认对象回收之后才可以将弱引用表中的指针设置为null。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;代：&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;提起.Net的垃圾回收，c++或者c程序员可能就会想，这么管理内存会不会出现性能问题呢。GC的开发人员一直在调整垃圾回收器提升它的性能。代就是一种为了降低垃圾回收对性能影响的机制。垃圾回收器在工作时会假定如下说法是成立的：&lt;/p&gt;&#xD;
&lt;p&gt;1. 一个对象越新，那么这个对象的生命周期就越短&lt;br /&gt;2. 一个对象越老，那么这个对象的生命周期就越长&lt;br /&gt;3. 新对象之间通常更可能和新对象之间存在引用关系&lt;br /&gt;4. 压缩堆的一部分要比压缩整个堆要快&lt;/p&gt;&#xD;
&lt;p&gt;当然大量研究证明以上几个假设在很多程序上是成立的。那就让我们来谈谈这几个假设是如何影响垃圾回收器工作的吧。&lt;/p&gt;&#xD;
&lt;p&gt;在程序初始化时，托管堆上没有对象。这时候新添到托管堆上的对象是的代是0.如下图所示，0代对象是最年轻的对象，他们从来没有经过垃圾回收器的检查。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/11387/2011112508450240.gif" alt="" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;图1 托管堆上的0代对象&lt;/p&gt;&#xD;
&lt;p&gt;现在如果堆上添加了更多的对象，堆填满时就会触发垃圾回收。当垃圾回收器分析托管堆时，会构建一个垃圾对象（图2中浅紫色块）和非垃圾对象的图。所有没有被回收的对象会被移动压缩到堆的最底端。这些没有被回收掉的对象就成为了1代对象，如图2所示&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/11387/2011112508452745.gif" alt="" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;图2 托管堆上的0代1代对象&lt;/p&gt;&#xD;
&lt;p&gt;当堆上分配了更多的对象时，新对象被放在了0代区。如果0代堆填满了，就会触发一次垃圾回收。这时候活下来的对象成为1代对象被移动到堆的底部；再此发生垃圾回收后1代对象中存活下来的对象会提升为2代对象并被移动压缩。如图3所示：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/11387/2011112508460553.gif" alt="" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;图3 托管堆上的0、1、2代对象&lt;br /&gt;2代对象是目前垃圾回收器的最高代，当再次垃圾回收时，没有回收的对象的代数依然保持2.&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;垃圾回收分代为什么可以优化性能&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;如前所述，分代回收可以提高性能。当堆填满之后会触发垃圾回收，垃圾回收器可以只选择0代上的对象进行回收，而忽略更高代堆上的对象。然而，由于越年轻的对象生命周期越短，因此，回收0代堆可以回收相当多的内存，而且回收所耗的性能也比回收所有代对象要少得多。&lt;/p&gt;&#xD;
&lt;p&gt;这是分代垃圾回收的最简单优化。分代回收不需要便利整个托管堆，如果一个根对象引用了一个高代对象，那么垃圾回收器可以忽略高代对象和其引用对象的遍历，这会大大减少构建可达对象图的时间。&lt;/p&gt;&#xD;
&lt;p&gt;如果回收0代对象没有释放出足够的内存，垃圾回收器会尝试回收1代和0代堆；如果仍然没有获得足够的内存，那么垃圾回收器会尝试回收2，1，0代堆。具体会回收那一代对象的算法不是确定的，微软会持续做算法优化。&lt;/p&gt;&#xD;
&lt;p&gt;多数堆（像c-runtime堆）只要找到足够的空闲内存就分配给对象。因此，如果我连续分配多个对象时，这些对象的地址空间可能会相差几M。然而在托管堆上，连续分配的对象的内存地址是连续的。&lt;/p&gt;&#xD;
&lt;p&gt;前面的假设中还提到，新对象之间更可能存在相互引用关系。因此新对象分配到连续的内存上，你可以获得就近引用的性能优化（you gain performance from locality of reference）。这样的话很可能你的对象都在CPU的缓存中，这样CPU的很多操作就不需要去存取内存了。&lt;/p&gt;&#xD;
&lt;p&gt;微软的性能测试显示托管堆的分配速度比标准的win32 HeapAlloc方法还要快。这些测试也显示了200MHz的Pentium的CPU做一次0代回收时间可以小于1毫秒。微软的优化目的是让垃圾回收耗用的时间小于一次普通的页面错误。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;使用System.GC类控制垃圾回收&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;类型System.GC运行开发人员直接控制垃圾回收器。你可以通过GC.MaxGeneration属性获得GC的最高代数，目前最高代是定值2.&lt;/p&gt;&#xD;
&lt;p&gt;你可以调用GC.Collect()方法强制垃圾回收器做垃圾回收，Collect方法有两个重载：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;void GC.Collect(Int32 generation)&#xD;
void GC.Collect()&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;第一个方法允许你指定要回收那一代。你可以传0到GC.MaxGeneration的数字做参数，传0只做0代堆的回收，传1会回收1代和0代堆，而传2会回收整个托管堆。而无参数的方法调用GC.Collect(GC.MaxGeneration)相当于整个回收。&lt;/p&gt;&#xD;
&lt;p&gt;在通常情况下，不应该去调用GC.Collect方法；最好让垃圾回收器按照自己的算法判断什么时候该调用Collect方法。尽管如此，如果你确信比运行时更了解什么时候该做垃圾回收，你就可以调用Collect方法去做回收。比如说程序可以在保存数据文件之后做一次垃圾回收。比如你的程序刚刚用完一个长度为10000的大数组，你不再需要他了，就可以把它设置为null然后执行垃圾回收，缓解内存的压力。&lt;/p&gt;&#xD;
&lt;p&gt;GC还提供了WaitForPendingFinalizers方法。这个方法简单的挂起执行线程，知道Freachable队列中的清空之后，执行完所有队列中的Finalize方法之后才继续执行。&lt;/p&gt;&#xD;
&lt;p&gt;GC还提供了两个方法用来返回某个对象是几代对象，他们是&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;Int32 GC.GetGeneration(object o);&#xD;
Int32 GC.GetGeneration(WeakReference wr)&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;第一个方法返回普通对象是几代，第二个方法返回弱引用对象的代数。&lt;/p&gt;&#xD;
&lt;p&gt;下面的代码可以帮助你理解代的意义：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;private static void GenerationDemo() {&#xD;
  // Let's see how many generations the GCH supports (we know it's 2)&#xD;
  Display("Maximum GC generations: " + GC.MaxGeneration);&#xD;
&#xD;
  // Create a new BaseObj in the heap&#xD;
  GenObj obj = new GenObj("Generation");&#xD;
&#xD;
  // Since this object is newly created, it should be in generation 0&#xD;
  obj.DisplayGeneration(); // Displays 0&#xD;
&#xD;
  // Performing a garbage collection promotes the object's generation&#xD;
  GC.Collect();&#xD;
  obj.DisplayGeneration(); // Displays 1&#xD;
&#xD;
  GC.Collect();&#xD;
  obj.DisplayGeneration(); // Displays 2&#xD;
&#xD;
  GC.Collect();&#xD;
  obj.DisplayGeneration(); // Displays 2 (max generation)&#xD;
&#xD;
  obj = null; // Destroy the strong reference to this object&#xD;
&#xD;
  GC.Collect(0); // Collect objects in generation 0&#xD;
  GC.WaitForPendingFinalizers(); // We should see nothing&#xD;
&#xD;
  GC.Collect(1); // Collect objects in generation 1&#xD;
  GC.WaitForPendingFinalizers(); // We should see nothing&#xD;
&#xD;
  GC.Collect(2); // Same as Collect()&#xD;
  GC.WaitForPendingFinalizers(); // Now, we should see the Finalize &#xD;
  // method run&#xD;
&#xD;
  Display(-1, "Demo stop: Understanding Generations.", 0);&#xD;
}&#xD;
class GenObj{&#xD;
  public void DisplayGeneration(){&#xD;
    Console.WriteLine(&amp;ldquo;my generation is &amp;rdquo; + GC.GetGeneration(this));&#xD;
  }&#xD;
&#xD;
  ~GenObj(){&#xD;
    Console.WriteLine(&amp;ldquo;My Finalize method called&amp;rdquo;);&#xD;
  }&#xD;
}&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;垃圾回收机制的多线程性能优化&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;在前面的部分，我解释了GC的算法和优化，然后讨论的前提都是在单线程情况下的。而在真实的程序中，很可能是多个线程一起工作，多个线程一起操纵托管堆上的对象。当一个线程触发了垃圾回收，其他所有的线程都应该暂停访问任何引用对象（包括他们自己栈上引用的对象），因为垃圾回收器有可能要移动对象，修改对象的内存地址。&lt;/p&gt;&#xD;
&lt;p&gt;因此当垃圾回收器开始回收时，所有执行托管代码的线程必须挂起。运行时有几种不同的机制可以安全的挂起线程来执行垃圾回收。这一块的内部机制我不打算详细说明。但是微软会持续修改垃圾回收的机制来降低垃圾回收带来的性能损耗。&lt;/p&gt;&#xD;
&lt;p&gt;下面几段描述了垃圾回收器在多线程情况下是如何工作的：&lt;br /&gt;完全中断代码执行 当垃圾回收开始执行时，挂起所有应用程序线程。垃圾回收器随后将线程挂起的位置记录到一个just-in-time(JIT)编译器生成的表中，垃圾回收器负责将线程挂起的位置记录在表中，记录当前正在访问的对象，以及对象存放的位置（变量中，CPU寄存器中，等等）&lt;br /&gt;劫持：垃圾回收器可以修改线程的栈让返回地址指向一个特殊的方法，当当前执行的方法返回时，这个特殊的方法将会执行，挂起线程，这种改变线程执行路径的方式称为劫持线程。当垃圾回收完成之后，线程会重新返回到之前执行的方法上。&lt;/p&gt;&#xD;
&lt;p&gt;安全点： 当JIT编译器编译一个方法时，可以在某个点插入一段代码判断GC是否挂起，如果是，线程就挂起等待垃圾回收完成，然后线程重新开始执行。JIT编译器插入检查GC代码的位置被称作&amp;ldquo;安全点&amp;rdquo;&lt;/p&gt;&#xD;
&lt;p&gt;请注意，线程劫持允许正在执行非托管代码的线程在垃圾回收过程中执行。如果非托管代码不访问托管堆上的对象时这是没有问题的。如果这个线程当前执行非托管代码然后返回执行托管代码，这个线程将会被劫持，直到垃圾回收完成之后再继续执行。&lt;/p&gt;&#xD;
&lt;p&gt;除了我刚提到的集中机制之外，垃圾回收器还有其他改进来增强多线程程序中的对象内存分配和回收。&lt;/p&gt;&#xD;
&lt;p&gt;同步释放分配（Synchronization-free Allocations）：在一个多线程系统中，0代堆被分成几个区域，一个线程使用一个区域。这允许多线程同时分配对象，并不需要一个线程独占堆。&lt;/p&gt;&#xD;
&lt;p&gt;可伸缩回收(Scalable Collections)：在多线程系统中运行执行引擎的服务器版本（MXSorSvr.dll）.托管堆会被分成几个不同的区域，一个CPU一个区域。当回收初始化时，每个CPU执行一个回收线程，各个线程回收各自的区域。而工作站版本的执行引擎（MXCorWks.dll）不支持这个功能。&lt;/p&gt;&#xD;
&lt;p&gt;大对象回收&lt;/p&gt;&#xD;
&lt;p&gt;这一块就不翻译了，有一篇&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/21/dot_net_gc_large_object_heap.html" target="_blank"&gt;专门的文章&lt;/a&gt;谈这件事儿&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;监视垃圾回收&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;如果你安装了.Net framework你的性能计数器（开始菜单&amp;mdash;管理工具&amp;mdash;性能 进入）中就会有.Net CLR Memory一项，你可以从实例列表中选择某个程序进行观察，如下图所示。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/11387/2011112508490785.gif" alt="" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;这些性能指标的具体含义如下：&lt;/p&gt;&#xD;
&lt;table style="width: 100%;" border="1" cellspacing="0" cellpadding="0"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;性能计数器&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;说明&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;# Bytes in all Heaps&lt;/strong&gt;&lt;strong&gt;（所有堆中的字节数）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示以下计数器值的总和：&amp;ldquo;第 0 级堆大小&amp;rdquo;计数器、&amp;ldquo;第 1 级堆大小&amp;rdquo;计数器、&amp;ldquo;第 2 级堆大小&amp;rdquo;计数器和&amp;ldquo;大对象堆大小&amp;rdquo;计数器。此计数器指示在垃圾回收堆上分配的当前内存（以字节为单位）。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;# GC Handles&lt;/strong&gt;&lt;strong&gt;（&lt;/strong&gt;&lt;strong&gt;GC &lt;/strong&gt;&lt;strong&gt;处理数目）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示正在使用的垃圾回收处理的当前数目。垃圾回收处理是对公共语言运行库和托管环境外部的资源的处理。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;# Gen 0 Collections&lt;/strong&gt;&lt;strong&gt;（第&lt;/strong&gt;&lt;strong&gt; 2 &lt;/strong&gt;&lt;strong&gt;级回收次数）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示自应用程序启动后第 0 级对象（即最年轻、最近分配的对象）被垃圾回收的次数。&lt;/p&gt;&#xD;
&lt;p align="left"&gt;当第 0 级中的可用内存不足以满足分配请求时发生第 0 级垃圾回收。此计数器在第 0 级垃圾回收结束时递增。较高级的垃圾回收包括所有较低级的垃圾回收。当较高级（第 1 级或第 2 级）垃圾回收发生时此计数器被显式递增。&lt;/p&gt;&#xD;
&lt;p align="left"&gt;此计数器显示最近的观察所得值。&lt;strong&gt;_Global_&lt;/strong&gt;&amp;nbsp;计数器值不准确，应该忽略。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;# Gen 1 Collections&lt;/strong&gt;&lt;strong&gt;（第&lt;/strong&gt;&lt;strong&gt; 2 &lt;/strong&gt;&lt;strong&gt;级回收次数）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示自应用程序启动后对第 1 级对象进行垃圾回收的次数。&lt;/p&gt;&#xD;
&lt;p align="left"&gt;此计数器在第 1 级垃圾回收结束时递增。较高级的垃圾回收包括所有较低级的垃圾回收。当较高级（第 2 级）垃圾回收发生时此计数器被显式递增。&lt;/p&gt;&#xD;
&lt;p align="left"&gt;此计数器显示最近的观察所得值。&lt;strong&gt;_Global_&lt;/strong&gt;&amp;nbsp;计数器值不准确，应该忽略。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;# Gen 2 Collections&lt;/strong&gt;&lt;strong&gt;（第&lt;/strong&gt;&lt;strong&gt; 2 &lt;/strong&gt;&lt;strong&gt;级回收次数）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示自应用程序启动后对第 2 级对象进行垃圾回收的次数。此计数器在第 2 级垃圾回收（也称作完整垃圾回收）结束时递增。&lt;/p&gt;&#xD;
&lt;p align="left"&gt;此计数器显示最近的观察所得值。&lt;strong&gt;_Global_&lt;/strong&gt;&amp;nbsp;计数器值不准确，应该忽略。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;# Induced GC&lt;/strong&gt;&lt;strong&gt;（引发的&lt;/strong&gt;&lt;strong&gt; GC &lt;/strong&gt;&lt;strong&gt;的数目）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示由于对&amp;nbsp;&lt;a href="http://msdn.microsoft.com/zh-cn/library/system.gc.collect(v=vs.80).aspx"&gt;GC.Collect&lt;/a&gt;&amp;nbsp;的显式调用而执行的垃圾回收的峰值次数。让垃圾回收器对其回收的频率进行微调是切实可行的。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;# of Pinned Objects&lt;/strong&gt;&lt;strong&gt;（钉住的对象的数目）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示上次垃圾回收中遇到的钉住的对象的数目。钉住的对象是垃圾回收器不能移入内存的对象。此计数器只跟踪被进行垃圾回收的堆中的钉住的对象。例如，第 0 级垃圾回收导致仅枚举第 0 级堆中钉住的对象。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;# of Sink Blocks in use&lt;/strong&gt;&lt;strong&gt;（正在使用的接收块的数目）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示正在使用的同步块的当前数目。同步块是为存储同步信息分配的基于对象的数据结构。同步块保留对托管对象的弱引用并且必须由垃圾回收器扫描。同步块不局限于只存储同步信息；它们还可以存储 COM interop 元数据。该计数器指示与同步基元的过度使用有关的性能问题。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;# Total committed Bytes&lt;/strong&gt;&lt;strong&gt;（提交字节的总数）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示垃圾回收器当前提交的虚拟内存量（以字节为单位）。提交的内存是在磁盘页面文件中保留的空间的物理内存。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;# Total reserved Bytes&lt;/strong&gt;&lt;strong&gt;（保留字节的总数）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示垃圾回收器当前保留的虚拟内存量（以字节为单位）。保留内存是为应用程序保留（但尚未使用任何磁盘或主内存页）的虚拟内存空间。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;% Time in GC&lt;/strong&gt;&lt;strong&gt;（&lt;/strong&gt;&lt;strong&gt;GC &lt;/strong&gt;&lt;strong&gt;中时间的百分比）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示自上次垃圾回收周期后执行垃圾回收所用运行时间的百分比。此计数器通常指示垃圾回收器代表该应用程序为收集和压缩内存而执行的工作。只在每次垃圾回收结束时更新此计数器。此计数器不是一个平均值；它的值反映了最近观察所得值。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;Allocated Bytes/second&lt;/strong&gt;&lt;strong&gt;（每秒分配的字节数）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示每秒在垃圾回收堆上分配的字节数。此计数器在每次垃圾回收结束时（而不是在每次分配时）进行更新。此计数器不是一段时间内的平均值；它显示最近两个样本中观测的值的差除以取样间隔时间所得的结果。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;Finalization Survivors&lt;/strong&gt;&lt;strong&gt;（完成时存留对象数目）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示因正等待完成而从回收后保留下来的进行垃圾回收的对象的数目。如果这些对象保留对其他对象的引用，则那些对象也保留下来，但此计数器不对它们计数。&amp;ldquo;从第 0 级提升的完成内存&amp;rdquo;和&amp;ldquo;从第 1 级提升的完成内存&amp;rdquo;计数器表示因完成而保留下来的所有内存。&lt;/p&gt;&#xD;
&lt;p align="left"&gt;此计数器不是累积计数器；它在每次垃圾回收结束时由仅在该特定回收期间存留对象的计数更新。此计数器指示由于完成应用程序可能导致系统开销过高。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;Gen 0 heap size&lt;/strong&gt;&lt;strong&gt;（第&lt;/strong&gt;&lt;strong&gt; 2 &lt;/strong&gt;&lt;strong&gt;级堆大小）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示在第 0 级中可以分配的最大字节数；它不指示在第 0 级中当前分配的字节数。&lt;/p&gt;&#xD;
&lt;p align="left"&gt;当自最近回收后的分配超出此大小时发生第 0 级垃圾回收。第 0 级大小由垃圾回收器进行微调并且可在应用程序执行期间更改。在第 0 级回收结束时，第 0 级堆的大小是 0 字节。此计数器显示调用下一个第 0 级垃圾回收的分配的大小（以字节为单位）。&lt;/p&gt;&#xD;
&lt;p align="left"&gt;此计数器在垃圾回收结束时（而不是在每次分配时）进行更新。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;Gen 0 Promoted Bytes/Sec&lt;/strong&gt;&lt;strong&gt;（从第&lt;/strong&gt;&lt;strong&gt; 1 &lt;/strong&gt;&lt;strong&gt;级提升的字节数&lt;/strong&gt;&lt;strong&gt;/&lt;/strong&gt;&lt;strong&gt;秒）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示每秒从第 0 级提升到第 1 级的字节数。内存在从垃圾回收保留下来后被提升。此计数器是每秒创建的在相当长时间保留下来的对象的指示符。&lt;/p&gt;&#xD;
&lt;p align="left"&gt;此计数器显示在最后两个样本（以取样间隔持续时间来划分）中观察到的值之间的差异。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;Gen 1 heap size&lt;/strong&gt;&lt;strong&gt;（第&lt;/strong&gt;&lt;strong&gt; 2 &lt;/strong&gt;&lt;strong&gt;级堆大小）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示第 1 级中的当前字节数；此计数器不显示第 1 级的最大大小。不直接在此代中分配对象；这些对象是从前面的第 0 级垃圾回收提升的。此计数器在垃圾回收结束时（而不是在每次分配时）进行更新。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;Gen 1 Promoted Bytes/Sec&lt;/strong&gt;&lt;strong&gt;（从第&lt;/strong&gt;&lt;strong&gt; 1 &lt;/strong&gt;&lt;strong&gt;级提升的字节数&lt;/strong&gt;&lt;strong&gt;/&lt;/strong&gt;&lt;strong&gt;秒）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示每秒从第 1 级提升到第 2 级的字节数。在此计数器中不包括只因正等待完成而被提升的对象。&lt;/p&gt;&#xD;
&lt;p align="left"&gt;内存在从垃圾回收保留下来后被提升。不会从第 2 级进行任何提升，因为它是最旧的一级。此计数器是每秒创建的非常长时间保留下来的对象的指示符。&lt;/p&gt;&#xD;
&lt;p align="left"&gt;此计数器显示在最后两个样本（以取样间隔持续时间来划分）中观察到的值之间的差异。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;Gen 2 heap size&lt;/strong&gt;&lt;strong&gt;（第&lt;/strong&gt;&lt;strong&gt; 2 &lt;/strong&gt;&lt;strong&gt;级堆大小）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示第 2 级中当前字节数。不直接在此代中分配对象；这些对象是在以前的第 1 级垃圾回收期间从第 1 级提升的。此计数器在垃圾回收结束时（而不是在每次分配时）进行更新。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;Large Object Heap size&lt;/strong&gt;&lt;strong&gt;（大对象堆大小）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示大对象堆的当前大小（以字节为单位）。垃圾回收器将大于 20 KB 的对象视作大对象并且直接在特殊堆中分配大对象；它们不是通过这些级别提升的。此计数器在垃圾回收结束时（而不是在每次分配时）进行更新。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;Promoted Finalization-Memory from Gen 0&lt;/strong&gt;&lt;strong&gt;（从第&lt;/strong&gt;&lt;strong&gt; 1 &lt;/strong&gt;&lt;strong&gt;级提升的完成内存）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示只因等待完成而从第 0 级提升到第 1 级的内存的字节数。此计数器不是累积计数器；它显示在最后一次垃圾回收结束时观察到的值。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;Promoted Finalization-Memory from Gen 1&lt;/strong&gt;&lt;strong&gt;（从第&lt;/strong&gt;&lt;strong&gt; 1 &lt;/strong&gt;&lt;strong&gt;级提升的完成内存）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示只因等待完成而从第 1 级提升到第 2 级的内存的字节数。此计数器不是累积计数器；它显示在最后一次垃圾回收结束时观察到的值。如果最后一次垃圾回收就是第 0 级回收，此计数器则重置为 0。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;Promoted Memory from Gen 0&lt;/strong&gt;&lt;strong&gt;（从第&lt;/strong&gt;&lt;strong&gt; 1 &lt;/strong&gt;&lt;strong&gt;级提升的内存）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示在垃圾回收后保留下来并且从第 0 级提升到第 1 级的内存的字节数。此计数器中不包括那些只因等待完成而提升的对象。此计数器不是累积计数器；它显示在最后一次垃圾回收结束时观察到的值。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;&lt;strong&gt;Promoted Memory from Gen 1&lt;/strong&gt;&lt;strong&gt;（从第&lt;/strong&gt;&lt;strong&gt; 1 &lt;/strong&gt;&lt;strong&gt;级提升的内存）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;td valign="top"&gt;&#xD;
&lt;p align="left"&gt;显示在垃圾回收后保留下来并且从第 1 级提升到第 2 级的内存的字节数。此计数器中不包括那些只因等待完成而提升的对象。此计数器不是累积计数器；它显示在最后一次垃圾回收结束时观察到的值。如果最后一次垃圾回收就是第 0 级回收，此计数器则重置为 0。&lt;/p&gt;&#xD;
&lt;/td&gt;&#xD;
&lt;/tr&gt;&#xD;
&lt;/tbody&gt;&#xD;
&lt;/table&gt;&#xD;
&lt;p&gt;这个表来自&lt;a href="http://msdn.microsoft.com/zh-cn/library/x2tyfybc(v=vs.80).aspx"&gt;MSDN&lt;/a&gt;&amp;nbsp;&lt;/p&gt;&#xD;
&lt;p&gt;全文完。原文地址：&lt;a href="http://msdn.microsoft.com/zh-cn/magazine/bb985011(en-us).aspx"&gt;http://msdn.microsoft.com/zh-cn/magazine/bb985011(en-us).aspx&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;原作者是：Jeffrey Richter，感谢原作者&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yukaizhao/aggbug/2262570.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/25/dot_net_GC_2.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yukaizhao/archive/2011/11/23/dot_net_GC_1.html</id><title type="text">【译】.Net 垃圾回收机制原理（一）</title><summary type="text">有了Microsoft.Net clr中的垃圾回收机制程序员不需要再关注什么时候释放内存，释放内存这件事儿完全由GC做了，对程序员来说是透明的。尽管如此，作为一个.Net程序员很有必要理解垃圾回收是如何工作的</summary><published>2011-11-23T00:48:00Z</published><updated>2011-11-23T00:48:00Z</updated><author><name>玉开</name><uri>http://www.cnblogs.com/yukaizhao/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yukaizhao/archive/2011/11/23/dot_net_GC_1.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yukaizhao/archive/2011/11/23/dot_net_GC_1.html"/><content type="html">&lt;p&gt;有了Microsoft.Net clr中的垃圾回收机制程序员不需要再关注什么时候释放内存，释放内存这件事儿完全由GC做了，对程序员来说是透明的。尽管如此，作为一个.Net程序员很有必要理解垃圾回收是如何工作的。这篇文章我们就来看下.Net是如何分配和管理托管内存的，之后再一步一步描述垃圾回收器工作的算法机制。&lt;/p&gt;&#xD;
&lt;p&gt;为程序设计一个适当的内存管理策略是困难的也是乏味的，这个工作还会影响你专注于解决程序本身要解决的问题。有没有一种内置的方法可以帮助开发人员解决内存管理的问题呢？当然有了，在.Net中就是GC，垃圾回收。&lt;/p&gt;&#xD;
&lt;p&gt;让我们想一下，每一个程序都要使用内存资源：例如屏幕显示，网络连接，数据库资源等等。实际上，在一个面向对象环境中，每一种类型都需要占用一点内存资源来存放他的数据，对象需要按照如下的步骤使用内存：&lt;br /&gt;1. 为类型分配内存空间&lt;br /&gt;2. 初始化内存，将内存设置为可用状态&lt;br /&gt;3. 存取对象的成员&lt;br /&gt;4. 销毁对象，使内存变成清空状态&lt;br /&gt;5. 释放内存&lt;/p&gt;&#xD;
&lt;p&gt;这种貌似简单的内存使用模式导致过很多的程序问题，有时候程序员可能会忘记释放不再使用的对象，有时候又会试图访问已经释放的对象。这两种bug通常都有一定的隐藏性，不容易发现，他们不像逻辑错误，发现了就可以修改掉。他们可能会在程序运行一段时间之后内存泄漏导致意外的崩溃。事实上，有很多工具可以帮助开发人员检测内存问题，比如：任务管理器，System Monitor AcitvieX Control, 以及Rational的Purify。&lt;/p&gt;&#xD;
&lt;p&gt;而GC可以完全不需要开发人员去关注什么时候释放内存。然而，垃圾回收器并不是可以管理内存中的所有资源。有些资源垃圾回收器不知道该如何回收他们，这部分资源就需要开发人员自己写代码实现回收。在.Net framework中，开发人员通常会把清理这类资源的代码写到Close、Dispose或者Finalize方法中，稍后我们会看下Finalize方法，这个方法垃圾回收器会自动调用。&lt;/p&gt;&#xD;
&lt;p&gt;不过，有很多对象是不需要自己实现释放资源的代码的，比如：Rectangle，清空它只需要清空它的left，right，width，height字段就可以了，这垃圾回收器完全可以做。下面让我们来看下内存是如何分配给对象使用的。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;对象分配：&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;.Net clr把所有的引用对象都分配到托管堆上。这一点很像c-runtime堆，不过你不需要关注什么时候释放对象，对象会在不用时自动释放。这样，就出现一个问题，垃圾回收器是怎么知道一个对象不再使用该回收了呢？我们稍后解释这个问题。&lt;/p&gt;&#xD;
&lt;p&gt;现在有几种垃圾回收算法，每一种算法都为一种特定的环境做了性能优化，这篇文章我们关注的是clr的垃圾回收算法。让我们从一个基础概念谈起。&lt;/p&gt;&#xD;
&lt;p&gt;当一个进程初始化之后，运行时会保留一段连续的空白内存空间，这块内存空间就是托管堆。托管堆会记录一个指针，我们叫它NextObjPtr，这个指针指向下一个对象的分配地址，最初的时候，这个指针指向托管堆的起始位置。&lt;/p&gt;&#xD;
&lt;p&gt;应用程序使用new操作符创建一个新对象，这个操作符首先要确认托管堆剩余空间能放得下这个对象，如果能放得下，就把NextObjPtr指针指向这个对象，然后调用对象的构造函数，new操作符返回对象的地址。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/11387/2011112308391245.gif" alt="" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;图1托管堆&lt;/p&gt;&#xD;
&lt;p&gt;这时候，NextObjPtr指向托管堆上下一个对象分配的位置，图1显示一个托管堆中有三个对象A、B和C。下一个对象会放在NextObjPtr指向的位置（紧挨着C对象）&lt;/p&gt;&#xD;
&lt;p&gt;现在让我们再看一下c-runtime堆如何分配内存。在c-runtime堆，分配内存需要遍历一个链表的数据结构，直到找到一个足够大的内存块，这个内存块有可能会被拆分，拆分后链表中的指针要指向剩余内存空间，要确保链表的完好。对于托管堆，分配一个对象只是修改NextObjPtr指针的指向，这个速度是非常快的。事实上，在托管堆上分配一个对象和在线程栈上分配内存的速度很接近。&lt;/p&gt;&#xD;
&lt;p&gt;到目前为止，托管堆上分配内存的速度似乎比在c-runtime堆上的更快，实现上也更简单一些。当然，托管堆获得这个优势是因为做了一个假设：地址空间是无限的。很显然这个假设是错误的。必须有一种机制保证这个假设成立。这个机制就是垃圾回收器。让我们看下它如何工作。&lt;/p&gt;&#xD;
&lt;p&gt;当应用程序调用new操作符创建对象时，有可能已经没有内存来存放这个对象了。托管堆可以检测到NextObjPtr指向的空间是否超过了堆的大小，如果超过了就说明托管堆满了，就需要做一次垃圾回收了。&lt;/p&gt;&#xD;
&lt;p&gt;在现实中，在0代堆满了之后就会触发一次垃圾回收。&amp;ldquo;代&amp;rdquo;是垃圾回收器提升性能的一种实现机制。&amp;ldquo;代&amp;rdquo;的意思是：新创建的对象是年轻一代，而在回收操作发生之前没有被回收掉的对象是较老的对象。将对象分成几代可以允许垃圾回收器只回收某一代的对象，而不是回收所有对象。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;垃圾回收算法：&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;垃圾回收器检查看是否存在应用程序不再使用的对象。如果这样的对象存在，那么这些对象占用的空间就可以被回收（如果堆上没有足够的内存可用，那么new操作符就会抛出OutofMemoryException）。你可能会问垃圾回收器是怎样判断一个对象是否还在用呢？这个问题不太容易得到答案。&lt;br /&gt;每个应用程序都有一组根对象，根是一些存储位置，他们可能指向托管堆上的某个地址，也可能是null。例如，所有的全局和静态对象指针是应用程序的根对象，另外在线程栈上的局部变量/参数也是应用程序的根对象，还有CPU寄存器中的指向托管堆的对象也是根对象。存活的根对象列表由JIT（just-in-time）编译器和clr维护，垃圾回收器可以访问这些根对象的。&lt;/p&gt;&#xD;
&lt;p&gt;当垃圾回收器开始运行，它会假设托管堆上的所有对象都是垃圾。也就是说，假定没有根对象，也没有根对象引用的对象。然后垃圾回收器开始遍历根对象并构建一个由所有和根对象之间有引用关系对象构成的图。&lt;/p&gt;&#xD;
&lt;p&gt;图2显示，托管堆上应用程序的根对象是A，C，D和F，这几个对象就是图的一部分，然后对象D引用了对象H，那么对象H也被添加到图中；垃圾回收器会循环遍历所有可达对象。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/11387/2011112308400553.gif" alt="" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;图2 托管堆上的对象&lt;/p&gt;&#xD;
&lt;p&gt;垃圾回收器会挨个遍历根对象和引用对象。如果垃圾回收器发现一个对象已经在图中就会换一个路径继续遍历。这样做有两个目的：一是提高性能，二是避免无限循环。&lt;/p&gt;&#xD;
&lt;p&gt;所有的根对象都检查完之后，垃圾回收器的图中就有了应用程序中所有的可达对象。托管堆上所有不在这个图上的对象就是要做回收的垃圾对象了。构建好可达对象图之后垃圾回收器开始线性的遍历托管堆，找到连续垃圾对象块（可以认为是空闲内存）。然后垃圾回收器将非垃圾对象移动到一起（使用c语言中的memcpy函数），覆盖所有的内存碎片。当然，移动对象时要禁用所有对象的指针（因为他们都可能是错误的了）。因此垃圾回收器必须修改应用程序的根对象使他们指向对象的新内存地址。此外，如果某个对象包含另一个对象的指针，垃圾回收器也要负责修改引用。图3显示了一次回收之后的托管堆。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/11387/2011112308404189.gif" alt="" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;图3 回收之后的托管堆&lt;/p&gt;&#xD;
&lt;p&gt;如图3所示在回收之后，所有的垃圾对象都被标识出来，而所有的非垃圾对象被移动到一起。所有的非垃圾对象的指针也被修改成移动后的内存地址，NextObjPtr指向最后一个非垃圾对象的后面。这时候new操作符就可以继续成功的创建对象了。&lt;/p&gt;&#xD;
&lt;p&gt;如你看到的，垃圾回收会有显著的性能损失，这是使用托管堆的一个明显的缺点。 不过，要记着内存回收操作旨在托管堆慢了之后才会执行。在满之前托管堆的性能比c-runtime堆的性能好要好。运行时垃圾回收器还会做一些性能优化，我们在下一篇文章中谈论这个。&lt;/p&gt;&#xD;
&lt;p&gt;下面的代码说明了对象是如何被创建管理的：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;class Application {&#xD;
    public static int Main(String[] args) {&#xD;
&#xD;
      // ArrayList object created in heap, myArray is now a root&#xD;
      ArrayList myArray = new ArrayList();&#xD;
&#xD;
      // Create 10000 objects in the heap&#xD;
      for (int x = 0; x &amp;lt; 10000; x++) {&#xD;
         myArray.Add(new Object());    // Object object created in heap&#xD;
      }&#xD;
&#xD;
      // Right now, myArray is a root (on the thread's stack). So, &#xD;
      // myArray is reachable and the 10000 objects it points to are also &#xD;
      // reachable.&#xD;
      Console.WriteLine(a.Length);&#xD;
&#xD;
      // After the last reference to myArray in the code, myArray is not &#xD;
      // a root.&#xD;
      // Note that the method doesn't have to return, the JIT compiler &#xD;
      // knows&#xD;
      // to make myArray not a root after the last reference to it in the &#xD;
      // code.&#xD;
&#xD;
      // Since myArray is not a root, all 10001 objects are not reachable&#xD;
      // and are considered garbage.  However, the objects are not &#xD;
      // collected until a GC is performed.&#xD;
   }&#xD;
}&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;也许你会问，GC这么好，为什么ANSI C++中没有它呢？ 原因是垃圾回收器必须能找到应用程序的根对象列表，必须找到对象的指针。而在C++中对象的指针之间是可以相互转换的，没有办法知道指针指向的是一个什么对象的指针。在CLR中，托管堆知道对象的实际类型。而元数据（metadata）信息可以用来判断对象引用了什么成员对象。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;垃圾回收和Finalization&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;垃圾回收器提供了一个额外的功能，它可以在对象被标识为垃圾后自动调用其Finalize方法（前提是对象重写了object的Finalize方法）。&lt;/p&gt;&#xD;
&lt;p&gt;Finalize方法是object对象的一个虚方法，如果需要你可以重写这个方法，但是这个方法只能通过类似c++析构函数的方式重写。例如：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;class Foo&#xD;
{&#xD;
	~Foo(){&#xD;
		Console.WriteLine(&amp;ldquo;Foo Finalize&amp;rdquo;);&#xD;
}&#xD;
}&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;这里用过C++的程序员要特别注意，Finalize方法的写法和C++的析构函数完全一样，但是，.Net 中的Finalize方法和析构函数的却是不一样的，托管对象是不能被析构的，只能通过垃圾回收回收。&lt;/p&gt;&#xD;
&lt;p&gt;当你设计一个类时，&lt;span style="color: #ff0000;"&gt;最好避免重写Finalize方法&lt;/span&gt;，原因如下：&lt;br /&gt;1. 实现Finalize的对象会被提升到更老的&amp;ldquo;代&amp;rdquo;，这会增加内存压力，使对象和此对象的关联对象不能在成为垃圾的第一时间回收掉。&lt;br /&gt;2. 这些对象分配时间会更长&lt;br /&gt;3. 让垃圾回收器执行Finalize方法会明显的损耗性能。请记住，每一个实现了Finalize方法的对象都需要执行Finalize方法，如果有一个长度为10000的数组对象，每个对象都需要执行Finalize方法&lt;br /&gt;4. 重写Finalize方法的对象可能会 引用其他没有实现Finalize方法的对象，这些对象也会延迟回收&lt;br /&gt;5. 你没有办法控制什么时候执行Finalize方法。如果要在Finalize方法中释放类似数据库连接之类的资源，就有可能导致数据库资源在时候后很久才得以释放&lt;br /&gt;6. 当程序崩溃时，一些对象还被引用，他们的Finalize方法就没有机会执行了。这种情况会在后台线程使用对象，或者对象在程序退出时，或者AppDomain卸载时。另外，默认情况下，当应用程序被强制结束时Finalize方法也不会执行。当然所有的操作系统资源会被回收；但是在托管堆上的对象不会回收。你可以通过调用GC的RequestFinalizeOnShutdown方法改变这种行为。&lt;br /&gt;7. 运行时不能控制多个对象Finalize方法执行的顺序。而有时候对象的销毁可能有顺序性&lt;/p&gt;&#xD;
&lt;p&gt;如果你定义的对象必须实现Finalize方法，那么要确保Finalize方法尽可能快的执行，要避免所有可能引起阻塞的操作，包括任何线程同步操作。另外，要确保Finalize方法不会引起任何异常，如果有异常垃圾回收器会继续执行其他对象的Finalize方法直接忽略掉异常。&lt;/p&gt;&#xD;
&lt;p&gt;当编译器生成代码时会自动在构造函数上调用基类的构造函数。同样C++的编译器也会为析构函数自动添加基类析构函数的调用。但是，.Net中的Finalize函数不是这样子，编译器不会对Finalize方法做特殊处理。如果你想在Finalize方法中调用父类的Finalize方法，必须自己显示添加调用代码。&lt;/p&gt;&#xD;
&lt;p&gt;请注意在C#中Finalize方法的写法和c++中的析构函数一样，但是C#不支持析构函数，不要让这种写法欺骗你。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;GC调用Finalize方法的内部实现&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;表面看，垃圾回收器嗲用Finalize方法很简单，你创建一个对象，当对象回收时调用它的Finalize方法。但是事实上要复杂一些。&lt;/p&gt;&#xD;
&lt;p&gt;当应用程序创建一个新对象时，new操作符在堆上分配内存。如果对象实现了Finalize方法。对象的指针会放到终结队列中。终结队列是由垃圾回收器控制的内部数据结构。在队列中每一个对象在回收时都需要调用它们的Finalize方法。&lt;/p&gt;&#xD;
&lt;p&gt;下图显示的堆上包含几个对象，其中一些对象是跟对象，一些对象不是。当对象C、E、F、I和J创建时，系统会检测这些对象实现了Finalize方法，并将它们的指针放到终结队列中。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/11387/2011112308431019.gif" alt="" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;Finalize方法要做的事情通常是回收垃圾回收器不能回收的资源，例如文件句柄，数据库连接等等。&lt;/p&gt;&#xD;
&lt;p&gt;当垃圾回收时，对象B、E、G、H、I和J被标记为垃圾。垃圾回收器扫描终结队列找到这些对象的指针。当发现对象指针时，指针会被移动到Freachable队列。Freachable队列是另一个由垃圾回收器控制的内部数据结构。在Freachable队列中的每一个对象的Finalize方法将执行。&lt;/p&gt;&#xD;
&lt;p&gt;垃圾回收之后，托管堆如图6所示。你可以看到对象B、G、H已经被回收了，因为这几个对象没有Finalize方法。然而对象E、I、J还没有被回收掉，因为他们的Finalize方法还没有执行。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/11387/2011112308433982.gif" alt="" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;图5 垃圾回收后的托管堆&lt;/p&gt;&#xD;
&lt;p&gt;程序运行时会有一个专门的线程负责调用Freachable队列中对象的Finalize方法。当Freachable队列为空时，这个线程会休眠，当队列中有对象时，线程被唤醒，移除队列中的对象，并调用它们的Finalize方法。因此在执行Finalize方法时不要企图访问线程的local storage。&lt;/p&gt;&#xD;
&lt;p&gt;终结队列（finalization queue）和Freachable队列之间的交互很巧妙。首先让我告诉你freachable的名字是怎么来的。F显然是finalization；在此队列中的每一个对象都在等待执行他们的Finalize方法；reachable意思是这些对象来了。另一种说法，Freachable队列中的对象被认为是跟对象，就像是全局变量或静态变量。因此，如果一个对象在freachable队列中，那么这个对象就不是垃圾。&lt;/p&gt;&#xD;
&lt;p&gt;简短点说，当一个对象是不可达的，垃圾回收器会认为这个对象是垃圾。那么，当垃圾回收器将对象从终结队列移动到Freachable队列中，这些对象就不再是垃圾了，它们的内存也不会回收。从这一点上来讲，垃圾回收器已经完成标识垃圾，一些对象被标识成垃圾又被重新认为成非垃圾对象。垃圾回收器回收压缩内存，清空freachable队列，执行队列中每一个对象的Finalize方法。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/11387/2011112308444115.gif" alt="" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;图6 再次执行垃圾回收后的托管堆&lt;/p&gt;&#xD;
&lt;p&gt;再次出发垃圾回收之后，实现Finalize方法的对象才被真正的回收。这些对象的Finalize方法已经执行过了，Freachable队列清空了。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;垃圾回收让对象复活&lt;/strong&gt;&lt;br /&gt;在前面部分我们已经说了，当程序不使用某个对象时，这个对象会被回收。然而，如果对象实现了Finalize方法，只有当对象的Finalize方法执行之后才会认为这个对象是可回收对象并真正回收其内存。换句话说，这类对象会先被标识为垃圾，然后放到freachable队列中复活，然后执行Finalize之后才被回收。正是Finalize方法的调用，让这种对象有机会复活，我们可以在Finalize方法中让某个对象强引用这个对象；那么垃圾回收器就认为这个对象不再是垃圾了，对象就复活了。&lt;/p&gt;&#xD;
&lt;p&gt;如下复活演示代码：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;public class Foo {&#xD;
  ~Foo(){&#xD;
  Application.ObjHolder = this;&#xD;
}&#xD;
}&#xD;
&#xD;
class Application{&#xD;
  static public Object ObjHolder = null;&#xD;
} &#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;在这种情况下，当对象的Finalize方法执行之后，对象被Application的静态字段ObjHolder强引用，成为根对象。这个对象就复活了，而这个对象引用的对象也就复活了，但是这些对象的Finalize方法可能已经执行过了，可能会有意想不到的错误发生。&lt;/p&gt;&#xD;
&lt;p&gt;事实上，当你设计自己的类型时，对象的终结和复活有可能完全不可控制。这不是一个好现象；处理这种情况的常用做法是在类中定义一个bool变量来表示对象是否执行过了Finalize方法，如果执行过Finalize方法，再执行其他方法时就抛出异常。&lt;/p&gt;&#xD;
&lt;p&gt;现在，如果有其他的代码片段又将Application.ObjHolder设置为null，这个对象变成不可达对象。最终垃圾回收器会把对象当成垃圾并回收对象内存。请注意这一次对象不会出现在finalization队列中，它的Finalize方法也不会再执行了。&lt;/p&gt;&#xD;
&lt;p&gt;复活只有有限的几种用处，你应该尽可能避免使用复活。尽管如此，当使用复活时，最好重新将对象添加到终结队列中，GC提供了静态方法ReRegisterForFinalize方法做这件事：&lt;br /&gt;如下代码:&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;public class Foo{&#xD;
  ~Foo(){&#xD;
  Application.ObjHolder = this;&#xD;
  GC.ReRegisterForFinalize(this);&#xD;
}&#xD;
}&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;当对象复活时，重新将对象添加到复活队列中。需要注意的时如果一个对象已经在终结队列中，然后又调用了GC.ReRegisterForFinalize(obj)方法会导致此对象的Finalize方法重复执行。&lt;/p&gt;&#xD;
&lt;p&gt;垃圾回收机制的目的是为开发人员简化内存管理。&lt;br /&gt;下一篇我们谈一下弱引用的作用，垃圾回收中的&amp;ldquo;代&amp;rdquo;，多线程中的垃圾回收和与垃圾回收相关的性能计数器。&lt;/p&gt;&#xD;
&lt;p&gt;本文是翻译文章，源地址：&lt;br /&gt;&lt;a href="http://msdn.microsoft.com/zh-cn/magazine/bb985010(en-us).aspx" target="_blank"&gt;http://msdn.microsoft.com/zh-cn/magazine/bb985010(en-us).aspx&lt;/a&gt; &amp;nbsp;&lt;br /&gt;如果你的E文很好，请看原文，如果你发现我的译文中有什么毛病请拍砖。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yukaizhao/aggbug/2259710.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yukaizhao/archive/2011/11/23/dot_net_GC_1.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry></feed>
