二、基础知识
1、托管堆和垃圾回收
1、Windows 内存架构
要了解 C# 的内存分配机制,首先需要了解 Windows 内存分配的机制,毕竟 CLR 中的内存是从 Windows 上分配过来的。架构图如下:
2、CLR堆管理器
2.1、简介
CLR 堆管理器托管的内存划分成两大块。
a、按对象大小划分
所有小于 85000 byte 的对象都存放在【小对象堆(SOH)】,大于等于 85000 byte 的对象存放在【大对象堆(LOH)】。
b、按生存期划分。
CLR 假设一个新分配的对象往往更容易成为一个垃圾对象,所以回收这些对象的效率会更高,所以在【小对象堆(SOH)】做了一个【代机制】的划分,也就是 0代、1代、2代。
2.2、托管堆布局图
托管堆是由很多的段(segment)组成的,新生成的 segment 叫做临时段,其他的叫 segment 年长段。小对象堆有临时段的概念,大对象堆是没有的。结构如图:
托管堆是由段组成的,在小对象堆中,最新创建的是临时段,其他则是内存段,依次类推。所有的0代对象和1代对象都会分配在临时段上,2代对象会有一部分放在临时段上,其他的段,比如:内存段,永远存放的是2代对象。
2.3、工作站和服务器GC
CLR 有两种 GC 模式,分别是:工作站 GC 和 服务器GC。
a)、工作站GC
工作站GC 一般指具有窗口类的应用程序,比如:WinForm,WPF,SliverLight,Console,这样的程序只有一个托管堆。
效果如图:
b)、服务器GC
对于 Web 类的程序,一般默认使用 服务器GC,它的托管堆个数和当前机器 CPU 的核数一致。
如图:
3、对象分配
当我们在对象堆中分配一个对象时,大致流程如下:
a)、将一个对象分配到托管堆上。
b)、如果托管堆的空间不足,将会触发 GC。
c)、GC 触发之后,如果空间足够的话,就会存放对象。
d)、如果垃圾对象带有析构函数,那么将会进入到【可终结队列】中被执行。
过程如图:
4、dumpheap 命令介绍
为了能够高效的筛选托管堆中的对象,SOS 提供了一个【!dumpheap】命令,这个命令十分强大,可以帮助我们很方便的筛选。
三、调试过程
废话不多说,这一节是具体的调试过程,又可以说是眼见为实的过程,在开始之前,我还是要啰嗦两句,这一节分为两个部分,第一部分是测试的源码部分,没有代码,当然就谈不上测试了,调试必须有载体。第二部分就是根据具体的代码来证实我们学到的知识,是具体的眼见为实。
1、调试源码
1.1、Example_11_1_1
1.2、Example_11_1_2
这个项目很简单,就是建立一个 Asp.Net WebAPI 的项目,不需要写任何代码,使用默认功能就可以。
1.3、Example_11_1_3
2、眼见为实
项目的所有操作都是一样的,所以就在这里说明一下,但是每个测试例子,都需要重新启动,并加载相应的应用程序,加载方法都是一样的。流程如下:我们编译项目,打开 Windbg,点击【文件】----》【launch executable】附加程序,打开调试器的界面,程序已经处于中断状态。
2.1、我们查看 NT 堆和 GC 堆。
调试源码:Example_11_1_1
1)、我们先来查看一下 NT 堆。
当我们成功进入调试器界面,使用【g】命令,继续运行,我们会在12行代码【Console.ReadLine()】暂停,我们程序打印出了【Hello world】,我们点击调试器工具栏中的【break】按钮,就可以调试程序了。
我们使用【!address -summary 】命令,查看一下具体情况。
输出的内容还是不少的,列表中【Heap 10 13b000 ( 1.230 MB) 1.00% 0.03%】,这个就是 NT 堆。
我们也可以使用【!heap -s】命令,查看 NT 堆的详情。
1 0:006> !heap -s 2 3 4 ************************************************************************************************************************ 5 NT HEAP STATS BELOW 6 ************************************************************************************************************************ 7 NtGlobalFlag enables following debugging aids for new heaps: 8 tail checking 9 free checking 10 validate parameters 11 LFH Key : 0x0afd8ea9 12 Termination on corruption : ENABLED 13 Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast 14 (k) (k) (k) (k) length blocks cont. heap 15 ----------------------------------------------------------------------------- 16 00f20000 40000062 1020 556 1020 6 65 1 0 4 (第一个堆是进程堆,是 Win32函数用的) 17 01200000 40001062 60 12 60 1 2 1 0 0 18 01570000 40001062 60 12 60 1 2 1 0 0 19 02f00000 40001062 60 4 60 0 1 1 0 0 20 01540000 40041062 60 4 60 2 1 1 0 0 21 -----------------------------------------------------------------------------
2)、我们查看一下 GC 堆。
如果我们想查看 GC 堆,可以直接使用【!eeheap -gc】命令。
1 0:006> !eeheap -gc 2 Number of GC Heaps: 1 3 generation 0 starts at 0x02f11018 4 generation 1 starts at 0x02f1100c 5 generation 2 starts at 0x02f11000 6 ephemeral segment allocation context: none 7 segment begin allocated size 8 02f10000 02f11000 02f1871c 0x771c(30492) 9 Large object heap starts at 0x03f11000 10 segment begin allocated size 11 03f10000 03f11000 03f2a180 0x19180(102784) 12 Total Size: Size: 0x2089c (133276) bytes. 13 ------------------------------ 14 GC Heap Size: Size: 0x2089c (133276) bytes.
2.2、我们查看大对象和小对象分配机制
调试源码:Example_11_1_1
当我们成功进入调试器界面,使用【g】命令,继续运行,我们会在12行代码【Console.ReadLine()】暂停,我们程序打印出了【Hello world】,我们点击调试器工具栏中的【break】按钮,就可以调试程序了。
byte[] byte1 = new byte[10000];这行数组小于85000就应该在小对象堆中,byte[] byet2 = new byte[85000];这个数组对象大于85000 就会分配在大对象堆中。
我们先使用【!eeheap -gc】查看一下托管堆的情况。
1 0:006> !eeheap -gc 2 Number of GC Heaps: 1 3 generation 0 starts at 0x02f11018 4 generation 1 starts at 0x02f1100c 5 generation 2 starts at 0x02f11000 6 ephemeral segment allocation context: none 7 segment begin allocated size 8 02f10000 02f11000 02f1871c 0x771c(30492) 9 Large object heap starts at 0x03f11000(这个就是大对象堆) 10 segment begin allocated size 11 03f10000 03f11000 03f2a180 0x19180(102784) 12 Total Size: Size: 0x2089c (133276) bytes. 13 ------------------------------ 14 GC Heap Size: Size: 0x2089c (133276) bytes.
我们再使用【!dumpheap 03f11000 03f2a180】查看一下大对象堆的情况。
0:006> !dumpheap 03f11000 03f2a180 Address MT Size 03f11000 00f45368 10 Free 03f11010 00f45368 14 Free 03f11020 6dae2788 4872 03f12328 00f45368 14 Free 03f12338 6dae2788 524 03f12548 00f45368 14 Free 03f12558 6dae2788 4092 03f13558 00f45368 14 Free 03f13568 6dae2788 8172 03f15558 00f45368 14 Free 03f15568 6dae5c40 85012 Statistics: MT Count TotalSize Class Name 00f45368 6 80 Free 6dae2788 4 17660 System.Object[] 6dae5c40 1 85012 System.Byte[] Total 11 objects
红色标注的就是我们的 byte 数组,大小是 85012,为什么多了12,数组是引用类型,引用类型都有两个附加字段(8)和一个数组长度(4)的字段,共12,再加上数组的长度就是85012。
我们确认了 byet2 确实是在大对象堆中,我们继续看看 byet1是不是在小对象堆中。使用相同的命令【!dumpheap 02f11000 02f1871c】,这个地址就是小对象堆的起始地址和结束地址,就是【ephemeral segment allocation】临时段。
1 0:006> !dumpheap 02f11000 02f1871c 2 Address MT Size 3 02f11000 00f45368 10 Free 4 02f1100c 00f45368 10 Free 5 ..... 6 02f124fc 00f45368 10 Free 7 02f12508 6dae5c40 10012 8 02f14c24 6dae8b20 28 9 ..... 10 02f174f4 6db49b0c 16 11 12 Statistics: 13 MT Count TotalSize Class Name 14 ...... 15 6dae5c40 4 10818 System.Byte[] 16 Total 325 objects
红色标记的就是 byte 数组,我们使用【!DumpHeap /d -mt 6dae5c40】查看详情。
1 0:006> !DumpHeap /d -mt 6dae5c40 2 Address MT Size 3 02f12508 6dae5c40 10012 4 02f16710 6dae5c40 526 5 02f171cc 6dae5c40 268 6 02f174e8 6dae5c40 12 7 03f15568 6dae5c40 85012 8 9 Statistics: 10 MT Count TotalSize Class Name 11 6dae5c40 5 95830 System.Byte[] 12 Total 5 objects
红色标记的就是我们的 byte1 byte 数组。当然,我们可以使用命令【!do】查看详情。
1 0:006> !do 02f12508 2 Name: System.Byte[] 3 MethodTable: 6dae5c40 4 EEClass: 6dbe8ba8 5 Size: 10012(0x271c) bytes 6 Array: Rank 1, Number of elements 10000, Type Byte (Print Array) 7 Content: ................................................................................................................................ 8 Fields: 9 None
2.3、如何按生存期查看对象的分配。
调试源码:Example_11_1_1
当我们成功进入调试器界面,使用【g】命令,继续运行,我们会在12行代码【Console.ReadLine()】暂停,我们程序打印出了【Hello world】,我们点击调试器工具栏中的【break】按钮,就可以调试程序了。
其实,有关对象代的调试,我们已经做过了,这里正式测试一下,我们依然使用【!eeheap -gc】命令,就可以看到托管堆中的代了。
1 0:006> !eeheap -gc 2 Number of GC Heaps: 1 3 generation 0 starts at 0x02f11018(0代) 4 generation 1 starts at 0x02f1100c(1代) 5 generation 2 starts at 0x02f11000(2代) 6 ephemeral segment allocation context: none 7 segment begin allocated size 8 02f10000 02f11000 02f1871c 0x771c(30492) 9 Large object heap starts at 0x03f11000 10 segment begin allocated size 11 03f10000 03f11000 03f2a180 0x19180(102784) 12 Total Size: Size: 0x2089c (133276) bytes. 13 ------------------------------ 14 GC Heap Size: Size: 0x2089c (133276) bytes.
2.4、查看 Console GC 模式
调试源码:Example_11_1_1
当我们成功进入调试器界面,使用【g】命令,继续运行,我们会在12行代码【Console.ReadLine()】暂停,我们程序打印出了【Hello world】,我们点击调试器工具栏中的【break】按钮,就可以调试程序了。
我们可以使用【!eeversion】命令,查看GC模式。
1 0:006> !eeversion 2 4.8.4300.0 retail 3 Workstation mode(工作站模式) 4 SOS Version: 4.8.4300.0 retail build
我们也可以通过【!eeheap -gc】命令查看托管堆的个数查看。
1 0:006> !eeheap -gc 2 Number of GC Heaps: 1(只有一个托管堆,就是工作站模式) 3 generation 0 starts at 0x02f11018 4 generation 1 starts at 0x02f1100c 5 generation 2 starts at 0x02f11000 6 ephemeral segment allocation context: none 7 segment begin allocated size 8 02f10000 02f11000 02f1871c 0x771c(30492) 9 Large object heap starts at 0x03f11000 10 segment begin allocated size 11 03f10000 03f11000 03f2a180 0x19180(102784) 12 Total Size: Size: 0x2089c (133276) bytes. 13 ------------------------------ 14 GC Heap Size: Size: 0x2089c (133276) bytes.
2.5、查看 Asp.Net Web API 的 GC 模式
调试源码:Example_11_1_2
这里测试的源码时 Web API 项目,直接运行程序,然后我们通过 Windbg 的【attach to Process】命令来查看。附加进程的进程是【iisexpress】,效果如图:
如果你使用的是【Debug】运行的 WEBAPI,调试会失败,附加进程有误,如图:
在 Windbg中提示的具体错误:"The process that you are attempting to attach to is already being debugged. Only one debugger can be invasively attached to a process at a time. A non-invasive attach is still possible when another debugger is attached." ,意思就是:尝试附加到的进程已在调试中。一次只能将一个调试器侵入性附加到进程。把程序的运行模式改为【Release】模式,不用使用调试模式,快捷键:Ctrl+F5,就可以附加进程成功了。
我们可以使用【!eeheap -gc】命令查看一下服务器GC模式。
1 0:037> !eeheap -gc 2 Number of GC Heaps: 4(有四个堆,这既是服务器GC模式) 3 ------------------------------ 4 Heap 0 (000001ff28a2dc70) 5 generation 0 starts at 0x000001ff29283e08 6 generation 1 starts at 0x000001ff29121018 7 generation 2 starts at 0x000001ff29121000 8 ephemeral segment allocation context: none 9 segment begin allocated size 10 000001ff29120000 000001ff29121000 000001ff2a79a728 0x1679728(23566120) 11 Large object heap starts at 0x0000020329121000 12 segment begin allocated size 13 0000020329120000 0000020329121000 000002032941f2c0 0x2fe2c0(3138240) 14 Heap Size: Size: 0x19779e8 (26704360) bytes. 15 ------------------------------ 16 Heap 1 (000001ff28a5dc40) 17 generation 0 starts at 0x000002002923b6d0 18 generation 1 starts at 0x0000020029121018 19 generation 2 starts at 0x0000020029121000 20 ephemeral segment allocation context: none 21 segment begin allocated size 22 0000020029120000 0000020029121000 000002002a755fe8 0x1634fe8(23285736) 23 Large object heap starts at 0x0000020339121000 24 segment begin allocated size 25 0000020339120000 0000020339121000 00000203392c8ff0 0x1a7ff0(1736688) 26 Heap Size: Size: 0x17dcfd8 (25022424) bytes. 27 ------------------------------ 28 Heap 2 (000001ff28a87bf0) 29 generation 0 starts at 0x00000201291ebe00 30 generation 1 starts at 0x0000020129121018 31 generation 2 starts at 0x0000020129121000 32 ephemeral segment allocation context: none 33 segment begin allocated size 34 0000020129120000 0000020129121000 000002012993ffe8 0x81efe8(8515560) 35 Large object heap starts at 0x0000020349121000 36 segment begin allocated size 37 0000020349120000 0000020349121000 0000020349121018 0x18(24) 38 Heap Size: Size: 0x81f000 (8515584) bytes. 39 ------------------------------ 40 Heap 3 (000001ff28ab1ba0) 41 generation 0 starts at 0x00000202291ebab8 42 generation 1 starts at 0x0000020229121018 43 generation 2 starts at 0x0000020229121000 44 ephemeral segment allocation context: none 45 segment begin allocated size 46 0000020229120000 0000020229121000 000002022a711fe8 0x15f0fe8(23007208) 47 Large object heap starts at 0x0000020359121000 48 segment begin allocated size 49 0000020359120000 0000020359121000 00000203592d9ec8 0x1b8ec8(1806024) 50 Heap Size: Size: 0x17a9eb0 (24813232) bytes. 51 ------------------------------ 52 GC Heap Size: Size: 0x511d870 (85055600) bytes.
当然,我们也可以使用【!eeversion】查看 GC 的模式。
1 0:037> !eeversion 2 4.8.4300.0 free 3 Server mode with 4 gc heaps(服务器模式) 4 SOS Version: 4.8.4300.0 retail build
我的机器当前又4个CPU,截图如下:
所以,服务器 GC 模式就有 4个 托管堆。
2.6、【!dumpheap】命令使用简介
调试源码:Example_11_1_3
当我们成功进入调试器界面,使用【g】命令,继续运行,我们会在12行代码【Console.ReadLine()】暂停,我们程序打印出了【3 个 byte[] 分配完毕!】,我们点击调试器工具栏中的【break】按钮,就可以调试程序了,开始我们的筛选调试。
如果我们直接使用【!dumpheap】命令,列出的内容太多了,想要从中查找一个对象,好像大海捞针一样,所以我们就需要过滤。
a)、我们过滤对象大小在1000-2000之间的对象。
我们执行命令【!dumpheap -min 0n1000 -max0n2000】,0n是十进制,默认0x十六进制。
1 0:006> !dumpheap -min 0n1000 -max 0n2000 2 Address MT Size 3 03372514 6da95c40 1512 4 03375690 6da92c60 1660 5 6 Statistics: 7 MT Count TotalSize Class Name 8 6da95c40 1 1512 System.Byte[] 9 6da92c60 1 1660 System.Char[] 10 Total 2 objects
我们再来一个错误的演示,把0n去掉,执行如下;
1 0:006> !dumpheap -min 1000 -max 2000 2 Address MT Size 3 04371020 6da92788 4872 4 04373568 6da92788 8172 5 6 Statistics: 7 MT Count TotalSize Class Name 8 6da92788 2 13044 System.Object[] 9 Total 2 objects
把 0n 去掉,我们看看1000和2000是多少。
1 0:006> ? 1000 2 Evaluate expression: 4096 = 00001000 3 0:006> ? 2000 4 Evaluate expression: 8192 = 00002000
看到了把,查找的范围就是4000多到8000多,意思就不对了,所以一定要加上 0n。
b)、我要找到大于1000的对象,我们可以使用【!dumpheap -min 0n1000】,去掉 -max 就可以了。
1 0:006> !dumpheap -min 0n1000 2 Address MT Size 3 03372514 6da95c40 1512 4 03372afc 6da95c40 3512 5 03375690 6da92c60 1660 6 04371020 6da92788 4872 7 04372558 6da92788 4092 8 04373568 6da92788 8172 9 04375568 6da95c40 85012 10 11 Statistics: 12 MT Count TotalSize Class Name 13 6da92c60 1 1660 System.Char[] 14 6da92788 3 17136 System.Object[] 15 6da95c40 3 90036 System.Byte[] 16 Total 7 objects
都是我们要查找的对象。
c)、找到托管堆中的所有 System.String 对象,我们可以执行【!dumpheap -type String】命令。
我们可以使用【!dumpheap -stat】命令,统计一下托管堆对象的结果,我们只关注 String。
1 0:006> !dumpheap -stat 2 Statistics: 3 MT Count TotalSize Class Name 4 6da95468 1 12 System.Collections.Generic.GenericEqualityComparer`1[[System.String, mscorlib]] 5 ...... 6 6da924e4 166 5926 System.String 7 6da92788 6 17748 System.Object[] 8 6da95c40 6 90842 System.Byte[] 9 Total 337 objects
红色标记的我们要查找的字符串。
字符串对象还是挺多的,如果想看效果,可以点开查看。
当然,如果我们知道字符串的方法表,也可以找到的到,这个样更精确。【!dumpheap -type String】是模糊查找,所有和字符串相关都会找到,我们使用【!dumpheap -mt 6da924e4】,这个才最准确。
d)、我们可以搜索大对象堆或者小对象堆中的对象,我们可以执行【!dumpheap】命令,命令后可以跟 Segment 段的开始和结束地址。
我们查看小对象堆的对象。
1 0:006> !eeheap -gc 2 Number of GC Heaps: 1 3 generation 0 starts at 0x03371018 4 generation 1 starts at 0x0337100c 5 generation 2 starts at 0x03371000 6 ephemeral segment allocation context: none 7 segment begin allocated size 8 03370000 03371000 03377ff4 0x6ff4(28660) 9 Large object heap starts at 0x04371000 10 segment begin allocated size 11 04370000 04371000 0438a180 0x19180(102784) 12 Total Size: Size: 0x20174 (131444) bytes. 13 ------------------------------ 14 GC Heap Size: Size: 0x20174 (131444) bytes. 15 16 17 0:006> !dumpheap 03371000 03377ff4 18 Address MT Size 19 03371000 015c4e80 10 Free 20 0337100c 015c4e80 10 Free 21 03371018 015c4e80 10 Free 22 。。。。。。。
我们查看大对象堆的对象。
1 0:006> !eeheap -gc 2 Number of GC Heaps: 1 3 generation 0 starts at 0x03371018 4 generation 1 starts at 0x0337100c 5 generation 2 starts at 0x03371000 6 ephemeral segment allocation context: none 7 segment begin allocated size 8 03370000 03371000 03377ff4 0x6ff4(28660) 9 Large object heap starts at 0x04371000 10 segment begin allocated size 11 04370000 04371000 0438a180 0x19180(102784) 12 Total Size: Size: 0x20174 (131444) bytes. 13 ------------------------------ 14 GC Heap Size: Size: 0x20174 (131444) bytes. 15 0:006> !dumpheap 04371000 0438a180 16 Address MT Size 17 04371000 015c4e80 10 Free 18 04371010 015c4e80 14 Free 19 04371020 6da92788 4872 20 04372328 015c4e80 14 Free 21 04372338 6da92788 524 22 04372548 015c4e80 14 Free 23 04372558 6da92788 4092 24 04373558 015c4e80 14 Free 25 04373568 6da92788 8172 26 04375558 015c4e80 14 Free 27 04375568 6da95c40 85012 28 29 Statistics: 30 MT Count TotalSize Class Name 31 015c4e80 6 80 Free 32 6da92788 4 17660 System.Object[] 33 6da95c40 1 85012 System.Byte[] 34 Total 11 objects
2.7\
如果您发现该资源为电子书等存在侵权的资源或对该资源描述不正确等,可点击“私信”按钮向作者进行反馈;如作者无回复可进行平台仲裁,我们会在第一时间进行处理!
加入交流群
请使用微信扫一扫!