.Net 调式案例—实验3 内存(Memory)回顾 System.OutOfMemoryEx...

.Net 调式案例实验内存(Memory)回顾 System.OutOfMemoryException web

今天的调试课程中的主要问题是内存的研究。此次咱们将压力测试BuggyBits站点,制造高内存使用量并找出缘由。这个实验有点长,由于我要讲述内存问题研究的各个方面。一旦尼知道dump文件的中的一些东西和性能计数器中的一些东西的关联你就能够跳过一些方面。但我仍然建议收集性能日志,这样比较完整。 数据库

前面的案例和设置指导 windows

请参看前面发布的文章。 服务器

设置性能计数器日志 session

1)  浏览http://localhost/BuggyBits/Links.aspx来让w3wp.exe 进程启动。 app

2)  打开一个性能计数器,从开始/运行 中输入:perfmon.exe asp.net

3)  右键在点击“性能日志和警告/日志计数器”上点击“新建日志设置”,在弹出的对话框中输入:Lab3-Mem dom

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后至少要让他再运行2030秒)

检查性能计数器的日志

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 01LOH计数器是在每次收集后才更新的,不是在每次分配后。

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

Qdump文件多少大?在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  exesHeap = 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保留的,VADdotnet对象存放的地方,由于GC堆是virtual allocs 分配的。

QBusyPctBusy),PctTots)是什么意思?

APct(Tots) 显示的是整个虚拟地址空间中分配给不一样区域类型的百分比。PctBusy)显示的是保留的内存中分配给不一样区域的百分比。Pct(busy) 很显然是我最关心的一个。

QMEM_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比较一下。

AGC的堆大小是:GC Heap Size 0x2ba82880(732 440 704),它和性能计数器中的bytes in all heaps很接近。

Qlarge 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

001a90c0     1105      7413924      Free

790fd8c4    51311    721773112 System.String

Total 163943 objects

大部分的内存是被strings用掉了,这个不太正常,虽然strings 在应用中是最多见的,可是大约721MB的显然有点怪异,而且有3600Links(不管它们是什么),看起来有点奇怪。特别是由于有差很少数量的stringbuilds 出如今dump中。

Q:“size”那个行显示了什么?例如,“size”这行包含了什么?

A:若是咱们用命令!do Link 对象输出来,咱们看到有一个指针指向stringbuilderurl)和一个指针指向stringname),link对象的大小是16B,这个大小仅仅包含了指针的大小和其余一些开销(methos table 等)。

若是你运行 !objsize ,你会看见大小是高达20144 B ,这个大小是包含成员变量的,好比Link对象的大小和它引用的全部对象。

你看到了什么经过!dumpheap 输出的16B的每个link。它不包含成员变量的大小是由一些不一样缘由的:

 1)它将要花费很长的时间去计算大小。

 2 一些对象(假如是AB)可能都指向C对象,若是你使用 !objsize 计算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 MTmethod table),!dumpheap –stat 的输出结果的第一列。

!dumpheap -mt <string MT> -min 85000 -stat

!dumpheap -mt <string MT> -min 10000 -stat

!dumpheap -mt <string MT> -min 20000 -stat

!dumpheap -mt <string MT> -min 30000 -stat

!dumpheap -mt <string MT> -min 25000 -stat

   Q:大部分的string’在一个什么样的范围内?

   A:在 20000  25000 字节之间。

4)把那个范围内的string 输出来。

   !dumpheap -mt <string MT> -min 20000 -max 25000

   在这里,它们中的大部分是如出一辙的大小的,这是一个指引咱们向下前进的线索。

0:000> !dumpheap -mt 790fd8c4 -min 20000 -max 25000

------------------------------

Heap 0

 Address       MT     Size

02f1412c   790fd8c4    20020    

02f2d96c   790fd8c4    20020    

02f327c4   790fd8c4    20020    

02f3761c   790fd8c4    20020    

02f3c474   790fd8c4    20020    

02f412cc   790fd8c4    20020    

02f46124   790fd8c4    20020    

02f4af7c   790fd8c4    20020    

02f4fdd4   790fd8c4    20020    

02f54c2c   790fd8c4    20020    

...

5)把它们中的一些输出来看看里面是什么

!do <address of string>  ,地址是 !dumpheap -mt 输出的第一列。

0:000> !do 02f327c4

...

String: http://www.sula.cn

...

Q:这些string里面包含的是什么?

A:好像link.aspx 页面显示了link对象。

6)拣几个,看看它们被根化(rooted)到哪里(即为何它们不会被回收)。注意你可能须要尝试不一样的几个才行。

!gcroot <address of string>

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)->

02f327b0(System.Text.StringBuilder)->

02f327c4(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

663a1fc8        1           12 System.Web.Configuration.ImpersonateTokenRef

79116758        1           20 Microsoft.Win32.SafeHandles.SafeTokenHandle

791037c0        1           20 Microsoft.Win32.SafeHandles.SafeFileMappingHandle

79103764        1           20 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle

6639c104        1           20 System.Web.PerfInstanceDataHandle

663f6b5c        1           28 System.Web.Security.FileSecurityDescriptorWrapper

663a105c        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

7910a5c4        2          120 System.Runtime.Remoting.Contexts

...

Q:在这个命令的输出中列出了什么对象?

A:全部具备终结/析构器的都被注册到终结器队列中,当对象被垃圾收集时,终结器会运行析构函数,不然在dispose函数中终结过程会挂起。

Q:有多少个对象是出于“ready for finalization”,它是什么意思?

A:大约有36000个,这些对象是要被垃圾收集的,正在等待被终结。若是ready for finalization大于但没有显示任何信息,这是一个说明终结器线程被堵塞的最好时机。因此这些对象被堵住了等待终结,他们消耗了大部分的内存。

2)  找出终结线程,了解它正在干什么,运行!threads ,在列出的线程中查找带有“(Finalizer)”的线程。

3)  切换到终结线程,检查托管的和本地(原生)的调用堆栈。

~5s   (替换成真实的终结线程(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    

02a0f8fc 7d61cca8 [HelperMethodFrame: 02a0f8fc] System.Threading.Thread.SleepInternal(Int32)

02a0f950 0fe90ce8 Link.Finalize()

02a0fc1c 79fbcca7 [ContextTransitionFrame: 02a0fc1c]

02a0fcec 79fbcca7 [GCFrame: 02a0fcec]

Q:什么对象正在被终结?

A:看起来是一个link 对象。

Q:它正在干什么? 为何这个会致使高内存使用率?

A:终结link对象的终结器线程由于sleep 被堵住了。意味着终结器被堵住,进程中没有东西能够被终结。于是等待终结的进程都会仍然在内存中直到终结器醒来它们被终结为止。

查看代码,确认上面的分析

打开Link.cs 的代码,看看析构/终结(destructor/finalizer)的有疑问的代码。

相关文章
相关标签/搜索