一、简介
今天是《Net 高级调试》的第十一篇文章,这篇文章来的有点晚,因为,最近比较忙,就没时间写文章了。现在终于有点时间,继续开始我们这个系列。这篇文章我们主要介绍托管堆的架构,对象的分配机制,我们如何查找在托管堆上的对象,我学完这章,很多以前很模糊的概念,现在很清晰了,知道了对象代的分配,大对象堆和小对象堆的结构,了解了对象的生命周期,这些是 Net 框架的底层,了解更深,对于我们调试更有利。当然了,第一次看视频或者看书,是很迷糊的,不知道如何操作,还是那句老话,一遍不行,那就再来一遍,还不行,那就再来一遍,俗话说的好,书读千遍,其意自现。
如果在没有说明的情况下,所有代码的测试环境都是 Net Framewok 4.8,但是,有时候为了查看源码,可能需要使用 Net Core 的项目,我会在项目章节里进行说明。好了,废话不多说,开始我们今天的调试工作。
调试环境我需要进行说明,以防大家不清楚,具体情况我已经罗列出来。
操作系统:Windows Professional 10
调试工具:Windbg Preview(可以去Microsoft Store 去下载)
开发工具:Visual Studio 2022
Net 版本:Net Framework 4.8
CoreCLR源码:源码下载
二、基础知识
1、托管堆和垃圾回收
1、Windows 内存架构
要了解 C# 的内存分配机制,首先需要了解 Windows 内存分配的机制,毕竟 CLR 中的内存是从 Windows 上分配过来的。架构图如下:

2、CLR堆管理器
2.1、简介
CLR 堆管理器托管的内存划分成两大块。
a、按对象大小划分
所有小于 85000 byte 的对象都存放在【小对象堆(SOH)】,大于等于 85000 byte 的对象存放在【大对象堆(BOH)】。
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 namespace Example_11_1_1
- 2 {
- 3 internal class Program
- 4 {
- 5 static void Main(string[] args)
- 6 {
- 7 byte[] byte1 = new byte[10000];
- 8 byte[] byet2 = new byte[85000];
- 9 Console.WriteLine("Hello world!");
- 10 Console.ReadLine();
- 11 }
- 12 }
- 13 }
复制代码 View Code
1.2、Example_11_1_2
这个项目很简单,就是建立一个 Asp.Net WebAPI 的项目,不需要写任何代码,使用默认功能就可以。
1.3、Example_11_1_3
  - 1 namespace Example_11_1_3
- 2 {
- 3 internal class Program
- 4 {
- 5 static void Main(string[] args)
- 6 {
- 7 byte[] byte1 = new byte[85000];
- 8 byte[] byte2 = new byte[1500];
- 9 byte[] byet3 = new byte[3500];
- 10 Console.WriteLine("3 个 byte[] 分配完毕!");
- 11 Console.ReadLine();
- 12 }
- 13 }
- 14 }
复制代码 View Code
2、眼见为实
项目的所有操作都是一样的,所以就在这里说明一下,但是每个测试例子,都需要重新启动,并加载相应的应用程序,加载方法都是一样的。流程如下:我们编译项目,打开 Windbg,点击【文件】----》【launch executable】附加程序,打开调试器的界面,程序已经处于中断状态。
2.1、我们查看 NT 堆和 GC 堆。
调试源码:Example_11_1_1
1)、我们先来查看一下 NT 堆。
当我们成功进入调试器界面,使用【g】命令,继续运行,我们会在12行代码【Console.ReadLine()】暂停,我们程序打印出了【Hello world】,我们点击调试器工具栏中的【break】按钮,就可以调试程序了。
我们使用【!address -summary 】命令,查看一下具体情况。
  - 1 0:006> !address -summary
- 2
- 3
- 4 Mapping file section regions...
- 5 Mapping module regions...
- 6 Mapping PEB regions...
- 7 Mapping TEB and stack regions...
- 8 Mapping heap regions...
- 9 Mapping page heap regions...
- 10 Mapping other regions...
- 11 Mapping stack trace database regions...
- 12 Mapping activation context regions...
- 13
- 14 --- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
- 15 Free 57 f852b000 ( 3.880 GB) 97.00%
- 16 Image 191 2eb9000 ( 46.723 MB) 38.06% 1.14%
- 17 <unknown> 88 2670000 ( 38.438 MB) 31.31% 0.94%
- 18 MappedFile 18 1cec000 ( 28.922 MB) 23.56% 0.71%
- 19 Stack 21 700000 ( 7.000 MB) 5.70% 0.17%
- 20 Heap 10 13b000 ( 1.230 MB) 1.00% 0.03%
- 21 Other 7 5c000 ( 368.000 kB) 0.29% 0.01%
- 22 TEB 7 16000 ( 88.000 kB) 0.07% 0.00%
- 23 PEB 1 3000 ( 12.000 kB) 0.01% 0.00%
- 24
- 25 --- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
- 26 MEM_IMAGE 198 2ec3000 ( 46.762 MB) 38.09% 1.14%
- 27 MEM_PRIVATE 122 2ebc000 ( 46.734 MB) 38.07% 1.14%
- 28 MEM_MAPPED 23 1d46000 ( 29.273 MB) 23.84% 0.71%
- 29
- 30 --- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
- 31 MEM_FREE 57 f852b000 ( 3.880 GB) 97.00%
- 32 MEM_RESERVE 65 43a4000 ( 67.641 MB) 55.10% 1.65%
- 33 MEM_COMMIT 278 3721000 ( 55.129 MB) 44.90% 1.35%
- 34
- 35 --- Protect Summary (for commit) - RgnCount ----------- Total Size -------- %ofBusy %ofTotal
- 36 PAGE_EXECUTE_READ 32 25b2000 ( 37.695 MB) 30.70% 0.92%
- 37 PAGE_READONLY 90 bae000 ( 11.680 MB) 9.51% 0.29%
- 38 PAGE_WRITECOPY 30 399000 ( 3.598 MB) 2.93% 0.09%
- 39 PAGE_READWRITE 102 1f8000 ( 1.969 MB) 1.60% 0.05%
- 40 PAGE_READWRITE | PAGE_GUARD 16 28000 ( 160.000 kB) 0.13% 0.00%
- 41 PAGE_EXECUTE_READWRITE 8 8000 ( 32.000 kB) 0.03% 0.00%
- 42
- 43 --- Largest Region by Usage ----------- Base Address -------- Region Size ----------
- 44 Free 80010000 7f2f0000 ( 1.987 GB)
- 45 Image 6de80000 f55000 ( 15.332 MB)
- 46 <unknown> 2f22000 fee000 ( 15.930 MB)
- 47 MappedFile 19d4000 133d000 ( 19.238 MB)
- 48 Stack 1410000 fd000 (1012.000 kB)
- 49 Heap f20000 8b000 ( 556.000 kB)
- 50 Other ff480000 33000 ( 204.000 kB)
- 51 TEB ce1000 4000 ( 16.000 kB)
- 52 PEB ccc000 3000 ( 12.000 kB)
复制代码 View Code 输出的内容还是不少的,列表中【Heap 10 13b000 ( 1.230 MB) 1.00% 0.03%】,这个就是 NT 堆。
我们也可以使用【!heap -s】命令,查看 NT 堆的详情。- 1 0:006> !heap -s
- 2
- 3
- 4 ************************************************************************************************************************
- 5 <strong> NT HEAP STATS</strong> 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 <strong>Large object heap starts</strong> 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[]
- <strong>6dae5c40 </strong><strong>1 85012</strong><strong> System.Byte[]</strong>
- 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 <strong>6dae5c40 4 10818 System.Byte[]
- </strong>16 Total 325 objects
复制代码 红色标记的就是 byte 数组,我们使用【!DumpHeap /d -mt 6dae5c40】查看详情。- 1 0:006> !DumpHeap /d -mt 6dae5c40
- 2 Address MT Size
- 3 <strong>02f12508 6dae5c40 10012 </strong>
- 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 <strong>generation 0</strong> starts at 0x02f11018(0代)
- 4 <strong>generation 1</strong> starts at 0x02f1100c(1代)
- 5 <strong>generation 2</strong> 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 <strong>Workstation mode(工作站模式)
- </strong>4 SOS Version: 4.8.4300.0 retail build
复制代码 我们也可以通过【!eeheap -gc】命令查看托管堆的个数查看。- 1 0:006> !eeheap -gc
- 2 <strong>Number of GC Heaps: 1(只有一个托管堆,就是工作站模式)</strong>
- 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 <strong>6da924e4 166 5926 System.String
- </strong>7 6da92788 6 17748 System.Object[]
- 8 6da95c40 6 90842 System.Byte[]
- 9 Total 337 objects
复制代码 红色标记的我们要查找的字符串。
 View Code 字符串对象还是挺多的,如果想看效果,可以点开查看。
当然,如果我们知道字符串的方法表,也可以找到的到,这个样更精确。【!dumpheap -type String】是模糊查找,所有和字符串相关都会找到,我们使用【!dumpheap -mt 6da924e4】,这个才最准确。
 View Code
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\
四、总结
终于写完了,由于这周比较忙,写文章的速度也慢了下来了,没办法,要先把工作做好,工作是本分。还是老话,虽然很忙,写作过程也挺累的,但是看到了自己的成长,心里还是挺快乐的。学习过程真的没那么轻松,还好是自己比较喜欢这一行,否则真不知道自己能不能坚持下来。老话重谈,《高级调试》的这本书第一遍看,真的很晕,第二遍稍微好点,不学不知道,一学吓一跳,自己欠缺的很多。好了,不说了,不忘初心,继续努力,希望老天不要辜负努力的人。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |