<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>测试 - 初晴神社</title>
	<atom:link href="https://vki.cc/test/feed/" rel="self" type="application/rss+xml" />
	<link>https://vki.cc/category/greattruth/test/</link>
	<description>白露的博客</description>
	<lastBuildDate>Wed, 29 Aug 2018 07:24:02 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1</generator>

<image>
	<url>https://cdn.vki.cc/wp-content/uploads/2018/03/cropped-icemaplen-1-32x32.png</url>
	<title>测试 - 初晴神社</title>
	<link>https://vki.cc/category/greattruth/test/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>CLR中的垃圾回收（GC）机制</title>
		<link>https://vki.cc/greattruth/test/592/</link>
					<comments>https://vki.cc/greattruth/test/592/#comments</comments>
		
		<dc:creator><![CDATA[白露]]></dc:creator>
		<pubDate>Sat, 25 Aug 2018 13:40:13 +0000</pubDate>
				<category><![CDATA[咸鱼心得]]></category>
		<category><![CDATA[测试]]></category>
		<category><![CDATA[C#]]></category>
		<guid isPermaLink="false">http://www.icemaplen.com/?p=592</guid>

					<description><![CDATA[<p>眼看着同学们一个个陆续收到了offer，好方啊。。 从托管堆分配资源 在谈GC之前我们先看一看CLR中资源是如何创建的。 CLR要求所有对象都从托管堆分配。进程初始化时，CLR划出一个地址空间区域作为托管堆。并&#8230;</p>
<p><a href="https://vki.cc/greattruth/test/592/">CLR中的垃圾回收（GC）机制</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></description>
										<content:encoded><![CDATA[<p><span style="color: #808080;"><em>眼看着同学们一个个陆续收到了offer，好方啊。。</em></span></p>
<h2 id="%e4%bb%8e%e6%89%98%e7%ae%a1%e5%a0%86%e5%88%86%e9%85%8d%e8%b5%84%e6%ba%90">从托管堆分配资源</h2>
<p><span style="color: #808080;"><em>在谈GC之前我们先看一看CLR中资源是如何创建的。</em></span></p>
<p><span style="color: #000000;">CLR要求所有对象都从<strong>托管堆</strong>分配。进程初始化时，CLR划出一个地址空间区域作为托管堆。并维护一个指针，称之为<strong>NextObjPtr，</strong>这个指针指向<strong>下一个对象在堆中的分配位置</strong>。刚开始的时候，NextObjPtr为地址空间区域的基地址。怎么说呢，有些类似栈指针，每安排一个对象，栈顶指针就会移动一个对象的大小。</span></p>
<p>当然这个堆的大小是有上限的，如果超过了上限，即这个区域被非辣鸡对象占满，CLR会尝试分配更多的区域，或者尝试垃圾回收。直到整个进程地址空间都被填满，到那个时候会抛出<strong>OutOfMemoryException</strong>异常。</p>
<p>在创建对象时我们会使用<strong>new</strong>操作符，来看下使用new后在CLR中会执行哪些操作：</p>
<p>1.首先计算类型的字段（以及从基类型继承的字段）所需的字节数。</p>
<p>2.将这个字节数加上对象开销所需的字节数。每个对象有两个开销字段：<strong>类型对象指针</strong>和<strong>同步块索引</strong>。</p>
<blockquote><p>类型对象指针在程序运行，JIT编译时会用到，用来引用和对象对应的基本类型。</p>
<p>同步块索引我暂时只发现在垃圾回收时有用（之后会讲），不知道有没有别的用途。</p></blockquote>
<p>3.CLR检查区域中是否有分配对象所需的字节数。如果有，就在NextObjPtr指针指向的地址处放入对象，并将为对象分配的字节清零。然后调用类型的构造器（对象中的this的值就是NextObjPtr），接着new操作符返回对象引用，分配完成。</p>
<p>然后NextObjPtr的值会加上对象占用的总字节数得到一个新的值，即下次分配时的初始地址。</p>
<h2 id="%e5%9e%83%e5%9c%be%e5%9b%9e%e6%94%b6%e7%ae%97%e6%b3%95">垃圾回收算法</h2>
<p><em><span style="color: #808080;">应用程序调用new操作符创建对象时，可能没有足够的空间来分配。发现空间不够，CLR就执行垃圾回收操作。</span></em></p>
<p><span style="color: #000000;">.NET垃圾回收器采用的是mark-and-compact算法。这种算法只关心引用类型的变量，并将其成为<strong>根</strong>。</span></p>
<p>CLR开始垃圾回收时，首先<strong>暂停进程中的所有线程</strong>。这样可以防止线程在CLR检查期间访问对象并更改状态。然后CLR进入垃圾回收的mark阶段，这时CLR遍历堆中所有对象，将同步块索引中的一位设为0。这表明所有对象都应该删除。然后CLR检查所有活动根，查看它们引用了哪些对象。任何根如果引用了堆上的对象，表明这个对象时可达的（还有用的），CLR将这个对象的同步块索引中的位从0改为1。一个对象被标记后，CLR会检查那个对象中的根，标记它们引用的对象。如果发现对象已经标记，就不重新检查对象的字段，这就避免了因为循环引用产生的死循环。</p>
<p>检查完成后，堆中的对象就被分成0（不可达）和1（可达）两部分，接下来CLR就要处理那些不可达的对象。即进入compact阶段，这个过程类似于磁盘碎片整理，并不是删除不可达对象，而是<strong>通过移动可达的对象，覆盖掉不可达的对象</strong>。这种做法不仅减小了应用程序的工作集，也解决了堆的空间碎片化问题。</p>
<p>不过完成上述步骤后还有一个问题需要解决。<strong>引用幸存对象的根还是指向移动前的位置</strong>，就好比你已经搬了家，但别人知道的还是你之前的地址。所以CLR还要从每个根减去所引用的对象在内存中偏移的字节数。这样就能保证每个根的引用还是和之前一样的对象。</p>
<p>compact之后，NextObjPtr指针指向最后一个幸存对象之后的位置。CLR恢复所有线程的运行。</p>
<p>如果在一次垃圾回收之后回收不了内存，而且进程中没有空间来分配新的GC区域，就说明该进程的内侧已耗尽。此时，试图分配更多内存的new操作符会抛出<strong>OutOfMemoryException</strong>异常。这可能导致操作系统终止进程并回收内存，即程序崩溃_(:з」∠)_</p>
<h2 id="%e4%bb%a3">代</h2>
<p>.NET垃圾回收的特别之处在于引入了“代”的概念，并非所有垃圾都一定会在一个垃圾回收周期中被回收。它有以下几点假设：</p>
<p>1.对象越新，生存期越短。</p>
<p>2.对象越老，生存期越长。</p>
<p>3.回收堆的一部分，速度快与回收整个堆。</p>
<p>即垃圾回收期会以更快的频率尝试清除生存时间较短的对象（新对象）。</p>
<p>总共分了0,1,2三代。新生对象被列为0代，假如第一次垃圾回收没有清除掉它，他就会提升至1代，同理直到提升到2代。相较于2代的对象，垃圾回收器会以更快的频率对第0代的对象执行垃圾回收。</p>
<p>除此之外，还有个优化效率的方法。上文提到如果一个对象被标记后，CLR会检查那个对象中的根，标记它们引用的对象。如果这个根引用了老一代的某个对象，垃圾回收器就可以忽略老对象内部的所有引用，也就能在更短时间内构造好可达对象图。当然老对象的字段也有可能引用新对象，为了确保对老对象的已更新字段进行检查，垃圾回收器利用了JIT编译器内部的一个机制，这个机制在对象的引用字段发生变化时，会设置一个对应的位标识。这样，垃圾回收器就知道自上一次垃圾回收以来，那些老对象（如果有的话）已被写入，只有字段发生变化的老对象才需检查是否引用了第0代中的任何新对象。</p>
<h2 id="%e5%9e%83%e5%9c%be%e5%9b%9e%e6%94%b6%e8%a7%a6%e5%8f%91%e6%9d%a1%e4%bb%b6">垃圾回收触发条件</h2>
<p>1.CLR检测到第0代的大小超出预算。</p>
<p>2.代码显式调用System.GC的静态Collect()方法。</p>
<p>尽量不要使用这种方法，因为在调用后会暂停所有线程的执行，所以降低性能。</p>
<p>不过在实现对性能要求高，多线程运行的方法前可以手动调用一次，尽量避免在方法运行时触发垃圾回收。</p>
<p>3.Windows报告低内存情况。</p>
<p>4.CLR卸载AppDomain。</p>
<p>5.CLR正在关闭。</p>
<h2 id="gc%e4%b8%8e%e7%bb%88%e7%bb%93%e5%99%a8">GC与终结器</h2>
<p>之前在q群看到有人问，既然有GC了为什么还需要终结器机制？</p>
<p>要回答这个问题首先要了解下托管资源和非托管资源：</p>
<ul>
<li>托管资源指的是.NET可以自动进行回收的资源，主要是指托管堆上分配的内存资源。托管资源的回收工作是不需要人工干预的，有.NET运行库在合适调用垃圾回收器进行回收。</li>
<li>非托管资源指的是.NET不知道如何回收的资源，最常见的一类非托管资源是包装操作系统资源的对象，例如文件，窗口，网络连接，数据库连接，画刷，图标等。这类资源，垃圾回收器在清理的时候会调用Object.Finalize()方法。默认情况下，方法是空的，对于非托管对象，需要在此方法中编写回收非托管资源的代码，以便垃圾回收器正确回收资源。</li>
</ul>
<p>简单说，GC只能处理堆上的资源，比如对象。而像文件句柄，数据库连接这些GC是无能为力的。需要我们手动释放它们，而终结器可以当做是保底机制，我们可以在终结器中写好清理非托管的资源的代码。假如我们忘记在代码中显式清理资源，当终结器被CLR调用的时候就可以帮我们清理。</p>
<hr />
<p>参考书籍;</p>
<p>1.《CLR via C#（第4版）》</p>
<p>2.《C# 6.0 本质论》</p>
<p><a href="https://vki.cc/greattruth/test/592/">CLR中的垃圾回收（GC）机制</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://vki.cc/greattruth/test/592/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
	</channel>
</rss>
