48 Console.WriteLine("Press any key to GC & promo to gen1");
49 Console.ReadKey();
50
51 GC.Collect();
52
53 Console.WriteLine("Press any key to GC & promo to gen2");
54 Console.ReadKey();
55
56 GC.Collect();
57
58 Console.WriteLine("Press any key to GC(free non pinned)");
59 Console.ReadKey();
60
61 for (int i = 0; i < numAllocs / 2; i++)
62 {
63 nonPinned[i] = null;
64 }
65
66 GC.Collect();
67
68 Console.WriteLine("Press any key to Exit");
69 Console.ReadKey();
70 }
71 }
72 }
复制代码
View Code 2.11、ExampleCore_5_10
1 using System.Xml.Serialization;
2
3 namespace ExampleCore_5_10
4 {
5 public class Person
6 {
7 private string? _name;
8 private string? _social;
9 private int _age;
10
11 public string? Name
12 {
13 get { return _name; }
14 set { _name = value; }
15 }
16
17 public string? SocialSecurity
18 {
19 get { return _social; }
20 set { _social = value; }
21 }
22
23 public int Age
24 {
25 get { return _age; }
26 set { _age = value; }
27 }
28
29 public Person()
30 {
31
32 }
33
34 public Person(string name, string socialSecurity, int age)
35 {
36 Name = name;
37 SocialSecurity = socialSecurity;
38 Age = age;
39 }
40 }
41
42 internal class Program
43 {
44 static void Main(string[] args)
45 {
46 Program program = new Program();
47 program.Run();
48 Console.WriteLine("Hello, World!");
49 }
50
51 public void Run()
52 {
53 XmlRootAttribute xmlRoot = new XmlRootAttribute();
54 xmlRoot.ElementName = "MyPersonRoot";
55 xmlRoot.Namespace = "http://www.contoso.com";
56 xmlRoot.IsNullable = true;
57
58 int index = 0;
59 while (true)
60 {
61
62 Person person = new Person();
63 person.Name = "Mario Hewardt";
64 person.SocialSecurity = "xxx-xx-xxxxxx";
65 person.Age = 56;
66
67 XmlSerializer xmlSerializer = new XmlSerializer(typeof(Person), xmlRoot);
68 Stream s = new FileStream("F:\\ser.txt", FileMode.Create);
69
70 xmlSerializer.Serialize(s, person);
71 s.Close();
72
73 index++;
74
75 Console.WriteLine($"已经序列化第【{index}】个人了!");
76 }
77 }
78 }
79 }
复制代码
View Code 三、基础知识 3.1、Windows 内存架构简介
A、基础知识
我们先上一张图,来了解一下 Windows 的内存架构。
在用户态(User Mode)中运行的进程通常会使用一个大概多个堆管理器。最常见的堆管理器有两个:Windows 堆管理器和 CLR 堆管理器。 Windows 堆管理器:它负责满足大多数的内存分配/回收的请求,它从 Windows 虚拟内存管理器中分配大块内存空间(称为内存段(Segment)),而且通过维持特定的记录数据(旁氏列表(Look Aside List)和 空闲列表(Free List)),以一种高效的方式将大块内存空间分割为许多更小的内存块来满足进程的分配需求。 CLR 堆管理器:和 Windows 堆管理器的功能类似,它为托管进程中全部的内存分配/回收请求提供服务。它同样也是从 Windows 虚拟内存管理器中分配大块内存(也称为内存段),使用这些内存段满足托管系统的内存分配/回收的请求。
这两种堆管理器的差别就是维持堆完备性时使用的记录数据的结构是不同的。
我们实行该下令,没有任何输出,因为在当前的托管堆中没有分配该类型的对象。我们【g】继续实行调试器,直到我们的控制台步伐输出“Press any key to Exit”时,再次点击【break】按钮,制止调试器的实行,再次实行【!DumpHeap -type ExampleCore_5_1.Name】下令。
1 1 0:001> !eeheap -gc
2 Number of GC Heaps: 1
3 generation 0 starts at 0x00000294D7C00028
4 generation 1 starts at 0x00000294D7800028
5 generation 2 starts at 0x000002D518170008
6 ephemeral segment allocation context: none
7 segment begin allocated committed allocated size committed size
B、眼见为实
调试源码:ExampleCore_5_3
调试任务:使用【!gcroot】下令查找对象的根引用。 1)、NTSD 调试
编译项目,打开【Visual Studio 2022 Developer Command Prompt v17.9.6】下令行工具,输入下令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_3\bin\Debug\net8.0\ExampleCore_5_3.exe】,打开【NTSD】调试器。
【g】直接运行调试器,直到调试器输出“Press any key to Exit”字样,效果如图:
按组合键【ctrl+c】进入制止模式,然后切换到托管线程上下文。
1 0:000> !eeheap -gc
2
3 ========================================
4 Number of GC Heaps: 1
5 ----------------------------------------
6 Small object heap
7 segment begin allocated committed allocated size committed size
B、眼见为实
调试源码:ExampleCore_5_4
调试任务:通过调试器观察带有闭幕器的对象是怎样被回收的。 1)、NTSD 调试
编译项目,打开【Visual Studio 2022 Developer Command Prompt v17.9.6】下令行工具,输入下令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_4\bin\Debug\net8.0\ExampleCore_5_4.exe】打开调试器。
进入调试器后,直接【g】运行步伐,直到调试器输出“Press any key to GC1!”字样。
2)、Windbg Preview 调试
编译项目,打开【Windbg Preview】调试器,依次点击【文件】---【Launch executable】加载我们的项目文件:ExampleCore_5_4.exe。进入调试器后,我们使用【g】下令,继续运行调试器,直到我们的控制台应用步伐输出“Press any key to GC1!”字样,如图:
如果 .NET 步伐需要通过互用性服务(例如平台调用大概COM 互用性)在 CLR 范围之外工作,对象的移动就会产生题目。如果某个托管对象的引用被传递给一个底层的非托管 API ,那么当非托管的 API 正在读取大概写入的内存同时移动了对象,就会导致严峻的题目。
我们举例阐明一下,先上一张图,然后详细阐明。
在上图中,在托管堆上起初包含了 5 个对象( A、B、C、D、E),对象 A 的地点 0x02000000,对象 C 的地点是 0x02000090。在某个特定时刻,通过平台调用来调用了一个异步的非托管的 API ,同时将对象 C 的地点(0x02000090)传递给 API。在调用这个非托管的异步 API 的时候发生了一次垃圾回收的操纵,使得对象 A 和对象 B 被回收。此时,托管堆上出现了空闲对象造成的缝隙,因而 垃圾收集器会通过紧缩托管堆来解决这个题目,因此,对象 C 移动到了地点 0x02000000 处,此地点以前是对象 A 的。此外,还归并了这两块空闲内存,并将它们放在堆的末尾。在完成了垃圾收集后,之前举行的异步 API 调用决定写入到最初传递给它的地点(0x02000090),因为当初保存的是对象 C。此时,再看,已经是物是人非了,被写入的内存不再由对象 C 占用,就会导致系统产生题目,排查题目也挺贫苦的。 怎样解决在 GC 实行紧缩时仍能安全的调用非托管代码,有一种解决方案就是固定。是指将托管堆上的某个对象固定住,垃圾收集器回收内存时就不会移动该对象,直到解除对这个对象的固定为止。
从输出中就可以看到,我们并没有看到 Run 方法的栈帧,只有 ExampleCore_5_6.Program.Main 栈帧了,阐明已经回收了,验证了我们的说法。
2)、Windbg Preview 调试
编译项目,打开【Windbg Preview】调试器,依次点击【文件】---【Launch executable】,加载我们的项目文件:ExampleCore_5_6.exe,进入到调试器。
进入到调试器,我们可以直接输入【g】下令,继续运行调试器,我们的控制台会输出“Press any key to Alloc And Pinned!”,如果此时,我们查找变量是一无所获的,因为还没有分配内存。我们继续在控制台步伐中按恣意键继续运行步伐,直到步伐输出“Press any key to GC1!”,效果如图:
我们从输出结果中可以看到,对象依然存在,因为该对象被固定住了。
我们继续实行【g】,控制台步伐输出“Press any key to GC2!”,阐明已经开释句柄,将要实行第二次垃圾回收。回到调试器,点击【break】按钮,制止调试器的实行,开始调试。
继续实行【!GCHandles】下令,查看句柄的情况。
赤色标注的就是发生变革的。3 个数组变量依然存在,3 个句柄变量已经开释了。
我们继续【g】实行调试器,控制台步伐输出“Press any key to Exit!”,此时,已经乐成实行第二垃圾回收,我们回到调试器,点击【break】按钮,进入到制止模式。此时,不消实行【!GCHandles】下令,句柄已经开释了。我们直接去托管线程上下文,查看调用栈,确认 3 个数组变量已经被回收了。
43 00000087DC9CE620 00007ffc8f3419a4 ExampleCore_5_6.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_6\Program.cs @ 13]
44 PARAMETERS:
45 args (0x00000087DC9CE670) = 0x00000197a6008e90
46 LOCALS:
47 0x00000087DC9CE658 = 0x00000197a6009628 Reason3 02543cbaec40 021428c02020 InvalidObjectReference Object 21428c02020 has a bad member at offset 10: 2142b4096c84 02543cbaf320 02142b409648 InvalidMethodTable Object 2142b409648 has an invalid method table 610061006100615 6 229 objects verified, 2 errors.
43 00000087DC9CE620 00007ffc8f3419a4 ExampleCore_5_6.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_6\Program.cs @ 13]
44 PARAMETERS:
45 args (0x00000087DC9CE670) = 0x00000197a6008e90
46 LOCALS:
47 0x00000087DC9CE658 = 0x00000197a6009628System.Buffers.SharedArrayPool6 Expected to find next object at 2142b409648, instead found it at 2142b4096f8.7 Heap local consistency not confirmed.