1232victor　　发布于　2013/04/03　11:26　.Net　调式案例　—　实验　3　内存(　Memory　)回顾　System.OutOfMemoryException　今天的调试课程中的主要问题是内存的研究。这次我们将压力测试　BuggyBits　站点,制造高内存使用量并找出原因。这个实验有点长,因为我要讲述内存问题研究的各个方面。一旦尼知道　dump　文件的中的一些东西和性能计数器中的一些东西的关联你就可以跳过一些方面。但我仍然建议收集性能日志,这样比较完整。　前面的案例和设置指导　请参看前面发布的文章。　设置性能计数器日志　1)　浏览　http://localhost/BuggyBits/Links.aspx　来让　w3wp.exe　进程启动。　2)　打开一个性能计数器,从开始　/　运行　中输入:　perfmon.exe　。　3)　右键在点击“性能日志和警告　/　日志计数器”上点击“新建日志设置”,在弹出的对话框中输入:　Lab3-Mem　。　4)　在接下来弹出的对话框中点击“添加对象　…　”。　5)　添加对象“　.NET　CLR　Memory　”和“　Process　”,点击关闭。　6)　把时间间隔设置成　1　秒,因为我们这个例子运行的时间比较短。　7)　修改运行方式,把“　<　默认　>　”修改成“　domain\username　”　(　把默认这个东西删除掉,把你机器的用户名输入进去　)　,然后点击设置密码,把你的用户名对应的密码输入进去。这样做的目的是你要以管理员(　administrator　)或具有对　w3wp.exe　调试权限的用户来运行日志计数器。否则将得不到　.net　计数器的值。　8)　点击确认,这个将会启动性能监视器对进程的监控。　问题再现　1)　在命令行中,切换到　tinyget　的目录。　2)　运行:　tinyget　-srv:localhost　-uri:/BuggyBits/Links.aspx　-loop:4000　,注意,如果你发现你的进程因为　OutOfMemory　异常崩溃了,你可以稍微降低循环的次数,我这里设置的这么高是想得到好一点的效果。　3)　停止性能计数器。(在运行　tinyget　后至少要让他再运行　20　到30　秒)　检查性能计数器的日志　1)　在性能监视器中,选择“系统监视器”这个节点。　2)　点击工具栏上“查看日志数据”(像数据库一样的那个图标),然后打开日志文件,请尽可能多的囊括更多的时间。　3)　把窗口下面那些默认的计数器都删除掉。　4)　添加一些计数器:　·　.NET　CLR　Memory/#　Bytes　in　all　Heaps　·　.NET　CLR　Memory/Large　Object　Heap　Size　·　.NET　CLR　Memory/Gen　2　heap　size　·　.NET　CLR　Memory/Gen　1　heap　size　·　.NET　CLR　Memory/Gen　0　heap　size　·　Process/Private　Bytes　·　Process/Virtual　Bytes　这些计数器将会在窗口底部显示出来。　5)　在图表任意位置上点击右键,选择“属性”,在“数据”的选项卡上在比例一栏中选择　0,0000001　,确保你可以在一个屏幕上看到在整个图表。如果你点击“突出显示”那个工具栏的按钮,那被选中的计数器就会高亮显示,更有利于知道那个计数器的含义。　Q　:这些计数器的最新值是什么?　A　:　Q　:比较一下　Virtual　Bytes,　Private　Bytes　和#Bytes　in　all　Heaps　,他们的曲线是不是一致的增长或下降,还是分歧很大?　A　:　这三个曲线　#Bytes　in　all　heaps　(　底部　),　Private　bytes　(　中间　)　和virtual　bytes　(　顶部　)　是一致的增长的,这意味着内存的增长是由于　#Bytes　in　all　heaps　(　.net　GC　memory　)的增长而增长的。　要查找出是什么类型的“内存泄露”,我们可以使用如下的规则:　a)　virtual　bytes　增长,　Private　bytes　保持平稳　=>　是virtual　bytes　泄漏,例如一些组件保留了一些内存,但没有使用它。　=>　使用调试工具来跟踪下去看看。　b)　Private　bytes　增长,　#Bytes　in　all　heaps　保持平稳　=>　本地(原生)或加载器堆(　loader　heap　)泄漏　=>　使用调试工具来跟踪查看,另外看看程序集(　assemblies　)的数量是不是增加了(看.net　clr　loading　下的计数器)。　c)　#Bytes　in　all　heaps　和private　bytes　彼此一起增长或下降　=>　研究　dotnet　GC　堆。　在virtual　bytes　和private　bytes　之间有　200-300M　的差别,在服务器进程中这是完全正常的,特别是在一个　dotnet　进程中,因为有很多内存被保留用来初始化　GC　堆(其他好比　dll　等这些事情),那些　virtual　bytes　的增长也是块状的,那也是正常的,因为堆(内存)典型的是以块申请的。　Q　:从这些数据你能否告诉我们这是本地(原生)内存泄露,还是　.net　内存泄露,或者是在装载堆(动态程序集)上的内存泄露?　A　:通过上面的观察,这是一个　.net　内存泄露。　Q　:在日志记录的结尾(当tinyget　运行结束后),内存保持平稳还是下降?　A　:它还是保持平稳,就是说在压力测试后内存没有被回收。　6)　添加　.NET　CLR　Memory/#　Gen　0　Collections,　.NET　CLR　Memory/#　Gen　1　Collections,　.NET　CLR　Memory/#　Gen　2　Collections　三个计数器。　Q　:当tinyget　运行完后,收集行为有发生么?如果没有,为什么呢?　A　:垃圾收集(GC)仅仅发生在分配内存时或你主动调用　GC.Collect()　时。这个概念是一个重点,请你记住。在我们的压力测试过后,我们并没有两个条件中的一个,因此没有人会收集那些内存,除非它必须要这么做(其他有需求时,操作系统来做,或dotnet　的其他程序)。　7)　打开任务管理器,在“进程”选项卡上,点击菜单上的“查看　/　选择列”,勾选上“虚拟内存大小”,把“内存使用”和“虚拟内存大小”的值与性能计数器中的“　Private　Bytes　”　和“　Virtual　Bytes　”进行比较,(注意:任务管理器中给出的是以　K　为单位的,你需要乘以　1024　)。　Q　:“内存使用”显示什么东西?　Q　:“虚拟内存大小”显示什么东西?　A　:在我这里,这两个指标在任务管理器显示了大约　746MB　,然后当我抓去　dump　文件时,　内存使用跳到了大约　864MB　,在这里的重点是理解这些值得含义。　另外关于性能监视器中的一些解释:　Private　Bytes　"the　current　size,　in　bytes,　of　memory　that　this　process　has　allocated　that　cannot　be　shared　with　other　processes."　Virtual　Bytes　"the　current　size,　in　bytes,　of　the　virtual　address　space　the　process　is　using.　Use　of　virtual　address　space　does　not　necessarily　imply　corresponding　use　of　either　disk　or　main　memory　pages.　Virtual　space　is　finite,　and　the　process　can　limit　its　ability　to　load　libraries."　Working　Set　"the　current　size,　in　bytes,　of　the　Working　Set　of　this　process.　The　Working　Set　is　the　set　of　memory　pages　touched　recently　by　the　threads　in　the　process.　If　free　memory　in　the　computer　is　above　a　threshold,　pages　are　left　in　the　Working　Set　of　a　process　even　if　they　are　not　in　use.　When　free　memory　falls　below　a　threshold,　pages　are　trimmed　from　Working　Sets.　If　they　are　needed　they　will　then　be　soft-faulted　back　into　the　Working　Set　before　leaving　main　memory."　#Bytes　in　all　heaps　"This　counter　is　the　sum　of　four　other　counters;　Gen　0　Heap　Size;　Gen　1　Heap　Size;　Gen　2　Heap　Size　and　the　Large　Object　Heap　Size.　This　counter　indicates　the　current　memory　allocated　in　bytes　on　the　GC　Heaps."　Gen　0　heap　size　"This　counter　displays　the　maximum　bytes　that　can　be　allocated　in　generation　0　(Gen　0);　its　does　not　indicate　the　current　number　of　bytes　allocated　in　Gen　0　.　A　Gen　0　GC　is　triggered　when　the　allocations　since　the　last　GC　exceed　this　size.　The　Gen　0　size　is　tuned　by　the　Garbage　Collector　and　can　change　during　the　execution　of　the　application.　At　the　end　of　a　Gen　0　collection　the　size　of　the　Gen　0　heap　is　infact　0　bytes;　this　counter　displays　the　size　(in　bytes)　of　allocations　that　would　trigger　the　next　Gen　0　GC.　This　counter　is　updated　at　the　end　of　a　GC;　its　not　updated　on　every　allocation.　"　Gen　1　heap　size　"This　counter　displays　the　current　number　of　bytes　in　generation　1　(Gen　1);　this　counter　does　not　display　the　maximum　size　of　Gen　1.　Objects　are　not　directly　allocated　in　this　generation;　they　are　promoted　from　previous　Gen　0　GCs.　This　counter　is　updated　at　the　end　of　a　GC;　its　not　updated　on　every　allocation."　Gen　2　heap　size　"This　counter　displays　the　current　number　of　bytes　in　generation　2　(Gen　2).　Objects　are　not　directly　allocated　in　this　generation;　they　are　promoted　from　Gen　1　during　previous　Gen　1　GCs.　This　counter　is　updated　at　the　end　of　a　GC;　its　not　updated　on　every　allocation."　LOH　size　"This　counter　displays　the　current　size　of　the　Large　Object　Heap　in　bytes.　Objects　greater　than　20　KBytes　85000　bytes　are　treated　as　large　objects　by　the　Garbage　Collector　and　are　directly　allocated　in　a　special　heap;　they　are　not　promoted　through　the　generations.　This　counter　is　updated　at　the　end　of　a　GC;　its　not　updated　on　every　allocation."　这里有一些注意的事情:　1)　Gen　0　heap　size　显示的是　Gen　0　的预算大小,不是在　Gen　0　中的所有对象的大小。　2)　Gen　0　,　1　,　2　和LOH　计数器是在每次收集后才更新的,不是在每次分配后。　3)　对于　LOH　的解释,　20K　是针对　1.0　版本的,在1.1　和2.0　后大对象的大小是大于　85000　字节的。　4)　这个进程的工作集环境是由一些　RAM　中的页面文件组成,这些页面文件是由该进程创建的,这个进程的　private　bytes　是没有什么事情可以做的(我们在查看任务管理器的时候,我们常常看内存使用量,它并不能代表什么),进城使用的内存量可能多于或少于　private　bytes　。对于　winform　的应用程序(没有服务进程的)有很大的差别,当你最小化一个　winform　程序时,程序会对内存进程对齐操作。而对于服务程序,例如　asp.net　不会这样,　private　bytes　和工作集会保持在同样的范围里面,不会改变。　得到一个　dump　文件　1)　在命令提示符下面,切换到调试器目录,运行:“　adplus　-hang　-pn　w3wp.exe　-quiet　”。　在WinDbg　中打开　dump　文件　1)　在WinDbg　中打开　dump　文件。　2)　设置符号文件路径和加载　sos　。　Q　:　dump　文件多少大?在windows　资源管理器中看看就知道了。　A:　864　096　k　Q　:这个文件的大小和　Private　Bytes,　Virtual　Bytes　和#Bytes　in　all　Heaps　比较一下,看看怎么样?　A　:看起来,它们都是差不多大的。　检查内存,想想内存去哪里了　1)　运行　!address　–summary　(这个命令给你一个内存使用的概要),让你自己熟悉一下这些输出。可以查看　WinDbg　的帮助看看　!address　。　0:000>　!address　-summary　Usage　SUMMARY　TotSize　(　KB)　Pct(Tots)　Pct(Busy)　Usage　373b7000　(　904924)　:　21.58%　85.85%　:　RegionUsageIsVAD　bfa89000　(　3140132)　:　74.87%　00.00%　:　RegionUsageFree　76e6000　(　121752)　:　02.90%　11.55%　:　RegionUsageImage　67c000　(　6640)　:　00.16%　00.63%　:　RegionUsageStack　0　(　0)　:　00.00%　00.00%　:　RegionUsageTeb　144a000　(　20776)　:　00.50%　01.97%　:　RegionUsageHeap　0　(　0)　:　00.00%　00.00%　:　RegionUsagePageHeap　1000　(　4)　:　00.00%　00.00%　:　RegionUsagePeb　1000　(　4)　:　00.00%　00.00%　:　RegionUsageProcessParametrs　2000　(　8)　:　00.00%　00.00%　:　RegionUsageEnvironmentBlock　Tot:　ffff0000　(4194240　KB)　Busy:　40567000　(1054108　KB)Type　SUMMARY　TotSize　(　KB)　Pct(Tots)　Usage　bfa89000　(　3140132)　:　74.87%　:　834e000　(　134456)　:　03.21%　:　MEM_IMAGE　95a000　(　9576)　:　00.23%　:　MEM_MAPPED　378bf000　(　910076)　:　21.70%　:　MEM_PRIVATE　State　SUMMARY　TotSize　(　KB)　Pct(Tots)　Usage　34bea000　(　864168)　:　20.60%　:　MEM_COMMIT　bfa89000　(　3140132)　:　74.87%　:　MEM_FREE　b97d000　(　189940)　:　04.53%　:　MEM_RESERVE　Largest　free　region:　Base　80010000　-　Size　7fefa000　(2096104　KB)　这里有非常多的信息,但所有的这些都不是这么明显的。相信我,让我们花一些时间在这里。在任何一个案例中,我用这个来帮助我指出我需要查看哪些地方,所以我不介意它要花费多少,即使就是一个概述的结果。　一些要注意的地方:　上面一屏显示了按照类型不同而分类显示的由进程使用的内存。第一部分是按照区域类型来划分的,它按照什么样子的分配类型告诉你信息。最常遇到的一个类型是　VAD　=　Virtual　Alloc,　Image　=　dlls　和exes　,　Heap　=　heaps　the　process　owns　,从WinDbg　的帮助中可以得到更多的信息。接着下面是按　IMAGE,　MAPPED　或PRIVATE　的类型来列出,最后一部分是按已提交(　also　committed　,就是指　实际已经分配的　)或保留(　reserved　)的方式来列出它们。　Tot:　ffff0000　(4　194　240　kb)　:的意思是我总共有　4GB　的虚拟内存地址空间提供给这个应用程序。　32　位系统上,你可以寻地　4GB　的空间,典型的是　2GB　的用户模式的内存空间,所以一般你会看到　2GB　而不是这里的　4GB　,在64　位上,运行一个　32　位的进程会得到完全的　4GB　的空间,所以我这里看到的是　4GB　。　Busy:　40567000　(1　054　108　kb)　是我们已经使用的(已经分配的)。　MEM_PRIVATE　是一个私有的内存,它不和其他进程共享内存,不是映射到文件的内存。不要把这个和性能计数器中的　Private　Bytes　混淆。这里的　MEM_PRIVATE　是保留　+　已提交(即已分配的)(　reserved　+　committed　)的字节数,另外那个　Private　Bytes　是申请　/　已提交(　allocated/committed　)的字节数。　MEM_PRIVATE　是已经提交(已经分配)的内存(不一定是　private　的),这个可能是最接近你得到的　Private　Bytes　的。　MEM_RESERVE　是已经保留的,但没有实际分配的,未提交的内存。所有已经分配的内存也是定义为保留的,所以如果你查看所有保留的内存(最接近你得到的　virtual　bytes　),你必须加上　MEM_COMMIT　和MEM_RESERVE　,它是显示在　Busy　中的那个数字。你自己把数字加上后比对一下看看。　Q　:哪个值最能代表如下两个指标?　·　Private　Bytes　A:　MEM_COMMIT　·　Virtual　Bytes　A:　Busy　Q　:大部分的内存都去哪里了?(哪个区域)　A　:在这里,大约　904MB　是为　VAD　保留的,　VAD　是dotnet　对象存放的地方,因为　GC　堆是　virtual　allocs　分配的。　Q　:　Busy　,　Pct　(　Busy　),　Pct　(　Tots　)是什么意思?　A　:　Pct(Tots)　显示的是整个虚拟地址空间中分配给不同区域类型的百分比。　Pct　(　Busy　)显示的是保留的内存中分配给不同区域的百分比。　Pct(busy)　很显然是我最关心的一个。　Q　:　MEM_IMAGE　是什么意思?　A　:从帮助文件中我们知道:这个是表示从一个可执行的映射文件的一部分映射到的内存。换句话说　就是　dll　或一个　exe　文件的内存映射。　Q　:哪个区域的　.net　内存是适宜的,为什么?　A　:在RegionUsageIsVAD　,理由如上。　从性能计数器中我们看到　#Bytes　in　all　Heaps　跟随着　Private　bytes　的增长而增长,那说明了内存的增加几乎都是　.net　的使用而增加的,进而我们转化为为什么　.net　的GC　堆(　heap　)始终在增长。　检查　.net　GC　堆(　heap　)　1)　运行　!eeheap　–gc　来查看　.net　GC　堆的大小　0:000>　!eeheap　-gc　Number　of　GC　Heaps:　2　Heap　0　(001aa148)　generation　0　starts　at　0x32f0639c　generation　1　starts　at　0x32ae3754　generation　2　starts　at　0x02eb0038　ephemeral　segment　allocation　context:　none　segment　begin　allocated　size　001bfe10　7a733370　7a754b98　0x00021828(137256)　001b0f10　790d8620　790f7d8c　0x0001f76c(128876)　…..　Large　object　heap　starts　at　0x0aeb0038　segment　begin　allocated　size　0aeb0000　0aeb0038　0aec0b28　0x00010af0(　68336　)　Heap　Size　0x15fd1310(368907024)Heap　1　(001ab108)　generation　0　starts　at　0x36e665bc　generation　1　starts　at　0x36a28044　generation　2　starts　at　0x06eb0038　ephemeral　segment　allocation　context:　none　segment　begin　allocated　size　06eb0000　06eb0038　0aea58d4　0x03ff589c(67066012)　……　Large　object　heap　starts　at　0x0ceb0038　segment　begin　allocated　size　0ceb0000　0ceb0038　0ceb0048　0x00000010(　16　)　Heap　Size　0x15ab1570(363533680)GC　Heap　Size　0x2ba82880(732440704)　Q　:总共有多少个　Heap　,为什么?　A　:这里有两个堆,因为我们运行在多核进程模型中。　Q　:有多少内存被保存在了　.net　GC　堆中?拿#Bytes　in　all　Heaps　比较一下。　A　:　GC　的堆大小是:　GC　Heap　Size　0x2ba82880(732　440　704)　,它和性能计数器中的　bytes　in　all　heaps　很接近。　Q　:　large　object　heap　上有多少内存?提示:把large　object　heap　段上的合计加起来,和性能计数器中的　Large　Object　Heap　Size　比较一下。　A　:它是非常小的,所以　LOH　看起来不是问题所在,大小是　68　336　+　16　bytes　2)　运行　!dumpheap　–stat　来输出所有的以统计式样表示的　.net　对象。　Q　:查看　5　到10　个使用了大部分内存的对象,思考一下是什么泄露了?　A　:　66424cf4　37　57276　System.Web.Caching.ExpiresEntry[]　663b0cdc　4001　192048　System.Web.SessionState.InProcSessionState　7912d8f8　3784　255028　System.Object[]　7912d9bc　820　273384　System.Collections.Hashtable+bucket[]　6639e4c0　4037　290664　System.Web.Caching.CacheEntry　0fe11cf4　36000　576000　Link　790fdc5c　36161　723220　System.Text.StringBuilder　001a　90c　0　1105　7413924　Free　790fd8c4　51311　721773112　System.String　Total　163943　objects　大部分的内存是被　strings　用掉了,这个不太正常,虽然　strings　在应用中是最常见的,但是大约　721MB　的显然有点怪异,并且有　3600　个Links　(无论它们是什么),看起来有点奇怪。特别是因为有差不多数量的　stringbuilds　出现在　dump　中。　Q　:“　size　”那个行显示了什么?例如,“　size　”这行包含了什么?　A　:如果我们用命令　!do　把Link　对象输出来　,　我们看到有一个指针指向　stringbuilder　(　url　)和一个指针指向　string　(　name　),　link　对象的大小是　16B　,这个大小仅仅包含了指针的大小和其他一些开销(　methos　table　等)。　如果你运行　!objsize　,你会看见大小是高达　20144　B　,这个大小是包含成员变量的,比如　Link　对象的大小和它引用的所有对象。　你看到了什么通过　!dumpheap　输出的　16B　的每一个　link　。它不包含成员变量的大小是由一些不同原因的:　1　)它将要花费很长的时间去计算大小。　2　)　一些对象(假如是　A　和B)可能都指向　C　对象,如果你使用　!objsize　计算　A　和B的大小,他们都会包含　C　的大小,所以　size　这个列的值会变得很复杂难以计算。　3　)　在这个例子中　Link　的大小　size　看起来似乎是正常的。因为一个　link　对象包含一个　url　和一个　name　。但是如果一个　web　控件可能会包含一个成员变量　_parent　,如果你运运行　!objsize　,这样就会包含父对象(　page　)那就显然是不合适的。　0:000>　!do　371d44cc　Name:　Link　MethodTable:　0fe11cf4　EEClass:　0fde5824　Size:　16　(0x10)　bytes　(C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\　Temporary　ASP.NET　Files\buggybits\b27906cc\f5f91899\App_Code.wbwztx_4.dll)　Fields:　MT　Field　Offset　Type　VT　Attr　Value　Name　790fdc5c　4000006　4　...ext.StringBuilder　0　instance　371d44dc　url　790fd8c4　4000007　8　System.String　0　instance　02f13cd8　name　0:000>　!objsize　371d44cc　sizeof(　371d44cc　)　=　20144　(　0x4eb0)　bytes　(Link)　通常,我不推荐立刻查看在你的这个非常简单的　dump　文件中,在该命令输出的底部的　strings　,因为:　·　strings　这一行的“　size　”是实际的字符串　string　的有内容的真实大小。如果你和　DataSet　比较,这个“　size　”只是包含了行和列的指针,并没有包含行和列的内存。所以　DataSet　这个对象的大小几乎总是非常的小的。　·　string　字符串在大部分的对象中几乎是叶子节点,例如,　dataset　包含字符串,　aspx　页面包含字符串,　session　变量也包含字符串。所以,在一个应用中几乎都是字符串。　然而在这个例子中,字符串有这么多,占有了那么多的内存。如果我们不查到其他一些阻止了我们的东西,那我们可能就要沿着　string　这条路走下去了。　3)　把各种不同大小的　string　都输出来,找出哪些　string　是越来越大的。(里面可能有一些讨厌的事和错误,所以你要尝试不同的大小来找出那些是越来越大的)　得到　string　的MT　(　method　table　),　!dumpheap　–stat　的输出结果的第一列。　!dumpheap　-mt　　-min　85000　-stat　!dumpheap　-mt　　-min　10000　-stat　!dumpheap　-mt　　-min　20000　-stat　!dumpheap　-mt　　-min　30000　-stat　!dumpheap　-mt　　-min　25000　-stat　Q　:大部分的　string’　在一个什么样的范围内?　A　:在20000　和25000　字节之间。　4　)把那个范围内的　string　输出来。　!dumpheap　-mt　　-min　20000　-max　25000　在这里,它们中的大部分是一模一样的大小的,这是一个指引我们向下前进的线索。　0:000>　!dumpheap　-mt　790fd8c4　-min　20000　-max　25000　Heap　0　Address　MT　Size　02f　1412c　790fd8c4　20020　02f　2d96c　790fd8c4　20020　02f　327c　4　790fd8c4　20020　02f　3761c　790fd8c4　20020　02f　3c　474　790fd8c4　20020　02f　412cc　790fd8c4　20020　02f　46124　790fd8c4　20020　02f　4af7c　790fd8c4　20020　02f　4fdd4　790fd8c4　20020　02f　54c　2c　790fd8c4　20020　...　5　)把它们中的一些输出来看看里面是什么　!do　　,地址是　!dumpheap　-mt　输出的第一列。　0:000>　!do　02f327c4　...　String:　http://www.sula.cn　...　Q　:这些　string　里面包含的是什么?　A　:好像　link.aspx　页面显示了　link　对象。　6　)拣几个,看看它们被根化(　rooted　)到哪里(即为什么它们不会被回收)。注意你可能需要尝试不同的几个才行。　!gcroot　　0:000>　!gcroot　02f327c4　Note:　Roots　found　on　stacks　may　be　false　positives.　Run　"!help　gcroot"　for　more　info.　Scan　Thread　16　OSTHread　1948　Scan　Thread　20　OSTHread　1b94　Scan　Thread　21　OSTHread　1924　Scan　Thread　22　OSTHread　188c　Scan　Thread　14　OSTHread　1120　Scan　Thread　24　OSTHread　13f8　Finalizer　queue:Root:02f327a0(Link)->　02f　327b0(System.Text.StringBuilder)->　02f　327c　4(System.String)　Q　:它们被根化到哪里?为什么?　A　:这个　string　是一个　string　builder　类型的成员变量,它表现的是一个　link　的成员变量(　url　),　link　对象被根化在终结器队列中,那就是说他正在等待被终结。　检查终结器队列(　finalizer　queue　)和终结线程(　finalizer　thread　)　1　)查看终结器队列　!finalizequeue　0:000>　!finalizequeue　SyncBlocks　to　be　cleaned　up:　0　MTA　Interfaces　to　be　released:　0　STA　Interfaces　to　be　released:　0　Heap　0　generation　0　has　221　finalizable　objects　(0f44a764->0f44aad8)　generation　1　has　0　finalizable　objects　(0f44a764->0f44a764)　generation　2　has　45　finalizable　objects　(0f44a6b0->0f44a764)　Ready　for　finalization　18009　objects　(0f44aad8->0f45c43c)Heap　1　generation　0　has　338　finalizable　objects　(0f45d840->0f45dd88)　generation　1　has　4　finalizable　objects　(0f45d830->0f45d840)　generation　2　has　36　finalizable　objects　(0f45d7a0->0f45d830)　Ready　for　finalization　17707　objects　(0f45dd88->0f46f234)　Statistics:　MT　Count　TotalSize　Class　Name　663a　1fc8　1　12　System.Web.Configuration.ImpersonateTokenRef　79116758　1　20　Microsoft.Win32.SafeHandles.SafeTokenHandle　791037c　0　1　20　Microsoft.Win32.SafeHandles.SafeFileMappingHandle　79103764　1　20　Microsoft.Win32.SafeHandles.SafeViewOfFileHandle　6639c　104　1　20　System.Web.PerfInstanceDataHandle　663f　6b5c　1　28　System.Web.Security.FileSecurityDescriptorWrapper　663a　105c　1　32　System.Web.Compilation.CompilationMutex　7910b630　2　40　System.Security.Cryptography.SafeProvHandle　79112728　5　100　Microsoft.Win32.SafeHandles.SafeWaitHandle　790fe704　2　112　System.Threading.Thread　7910a　5c　4　2　120　System.Runtime.Remoting.Contexts　...　Q　:在这个命令的输出中列出了什么对象?　A　:所有具有终结　/　析构器的都被注册到终结器队列中,当对象被垃圾收集时,终结器会运行析构函数,否则在　dispose　函数中终结过程会挂起。　Q　:有多少个对象是出于“　ready　for　finalization　”,它是什么意思?　A　:大约有　36000　个,这些对象是要被垃圾收集的,正在等待被终结。如果　ready　for　finalization　大于　0　但没有显示任何信息,这是一个说明终结器线程被堵塞的最好时机。所以这些对象被堵住了等待终结,他们消耗了大部分的内存。　2)　找出终结线程,了解它正在干什么,运行　!threads　,在列出的线程中查找带有“　(Finalizer)　”的线程。　3)　切换到终结线程,检查托管的和本地(原生)的调用堆栈。　~5s　(　把5替换成真实的终结线程(　finalizer　thread　)的ID　号)kb　2000　!clrstack　0:000>　!threads　...　20　2　1b94　001ac2c0　200b220　Enabled　00000000:00000000　001ccc80　0　MTA　(Finalizer)　...　0:020>　!clrstack　OS　Thread　Id:　0x1b94　(20)　ESP　EIP　02a　0f　8fc　7d61cca8　[HelperMethodFrame:　02a0f8fc]　System.Threading.Thread.SleepInternal(Int32)　02a　0f　950　0fe90ce8　Link.Finalize()　02a　0fc1c　79fbcca7　[ContextTransitionFrame:　02a0fc1c]　02a　0fcec　79fbcca7　[GCFrame:　02a0fcec]　Q　:什么对象正在被终结?　A　:看起来是一个　link　对象。　Q　:它正在干什么?　为什么这个会导致高内存使用率?　A　:终结　link　对象的终结器线程因为　sleep　被堵住了。意味着终结器被堵住,进程中没有东西可以被终结。因而等待终结的进程都会仍然在内存中直到终结器醒来它们被终结为止。　查看代码,确认上面的分析　打开　Link.cs　的代码,看看析构　/　终结(　destructor/finalizer　)的有疑问的代码。