赞
踩
参考链接:nvestigating .NET Memory Management and Garbage Collection
在 CLR 中,GC(垃圾收集器,Garbage Collection) 管理一切内存。之所以会有内存泄漏,是因为 GC 不能总是正确的释放内存。本文最后将介绍如何使用 SOS(Son of Strike)来检查内存和对象分配
参考:CLR
CLR(Common Language Runtime)是公共语言运行时环境
.NET 提供了一个称为公共语言运行时的运行时环境,可以编写利用托管执行环境的代码。 使用面向运行时的语言编译器开发的代码称为托管代码。
简而言之,就是类似于 Java 虚拟机,你写的代码运行在这上面,你申请的内存,释放的内存都由 CLR 进行管理。
托管代码具有许多优点
简单来说,就是在分配内存时,无论出于何种原因,你的程序运行后都不会释放该内存,于是就导致了内存泄漏。 在.NET语言中,您可以通过创建对象来分配内存,并通过允许对该对象的引用出现范围来释放内存。例如:
void MethodName()
{
Object o = new Object();
//Create a new Object and store a reference to it as o
DoSomethingWith(o);
//Use the new Object by passing the reference to o
o = null;
//Lose the reference to the new Object, it is now eligible for freeing
}
//o is now out of scope so can be freed
当 o 在作用域范围内,GC 可以检查对象是否有任何引用。 如果没有什么引用对象,则可以释放。 如果 DoSomethingWith
方法存储对象 o 的引用,那么当 GC 检查是否正在使用时,仍然会有引用,因此它将无法释放内存。
这里多次提到了引用一词, 当新建对象时,实际上不会返回对象,而是对在堆空间中创建的新对象的引用
只要 DoSomethingWith
不存储对对象的引用,当该对象到达该方法的末尾并超出范围时,引用将丢失,并且垃圾收集器可以释放内存。 因此,在这种情况下,没有内存泄漏
方法很简单,如果程序有内存泄漏,那么程序运行一段时间后,会捕获 OutofMemory(OOM)异常。 或者使用以下的方法实时监视内存增长情况:
Task.Run(async () => {
while (true) {
string memory = "";
using (Process proc = Process.GetCurrentProcess()) {
long currentMemory = proc.PrivateMemorySize64;
memory = $"Memory: {currentMemory} B";
}
Console.WriteLine(memory);
await Task.Delay(1000);
}
});
参考链接:debugging
关于调试器:Debugging Tools for Windows (WinDbg, KD, CDB, NTSD)
有两种类型的调试器(debugger):内核和用户模式(kernel 和 user-mode)。内核调试器被用来调试驱动程序和Windows内核。 用户模式调试者用于应用程序和服务。
本文只讨论用户模式下的 debugger,Windows调试工具包中有两个调试器,下载地址:Windows Debugger
这两个调试器都提供了对 dbgEng.dll 的封装,即调试的核心方法,它们的调试命令和返回值都是相同的,因此只需选择您喜欢的工具并坚持下去即可。这里推荐CDB。
.NET 应用程序的编写执行过程如下
举个列子:
① .net 代码
static void DoSomething()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine(String.Format("Number: {0}", i));
}
}
② 编译成 MSIL
.method private hidebysig static void DoSomething() cil managed { // Code size 43 (0x2b) .maxstack 2 .locals init ([0] int32 i, [1] bool CS$4$0000) IL_0000: nop IL_0001: ldc.i4.0 IL_0002: stloc.0 IL_0003: br.s IL_0021 IL_0005: nop IL_0006: ldstr "Number: {0}" IL_000b: ldloc.0 IL_000c: box [mscorlib]System.Int32 IL_0011: call string [mscorlib]System.String::Format(string, object) IL_0016: call void [mscorlib]System.Console::WriteLine(string) IL_001b: nop IL_001c: nop IL_001d: ldloc.0 IL_001e: ldc.i4.1 IL_001f: add IL_0020: stloc.0 IL_0021: ldloc.0 IL_0022: ldc.i4.s 10 IL_0024: clt IL_0026: stloc.1 IL_0027: ldloc.1 IL_0028: brtrue.s IL_0005 IL_002a: ret } // end of method Program::DoSomething
③ 转换成二进制文件
55 8b ec 83 ec 18 83 3d 14 2e 92 00 00 74 05 e8 c5 a3 f4 76 33 d2 89 55-fc c7 45 f8 00 00 00 00 90 33 d2 89 55 fc 90 eb 41 90 b9 38 2b 33 79 e8 40 1f 79 fd 89 45 f4 8b 05 30 20 fb 01 89 45 ec 8b 45 f4 8b 55 fc 89 50 04 8b 45 f4 89 45 e8 8b 4d ec 8b 55 e8 e8 ee 72 13 76 89 45 f0 8b 4d f0 e8 cb 36 61 76 90 90 ff 45 fc 83 7d fc 0a 0f 9c c0 0f b6 c0 89 45 f8 83-7d f8 00 75 ac 90 8b e5 5d c3
debugger 会解码字节并显示程序集,如下所示,注意到第二列显示了上述字节
031800a8 55 push ebp 031800a9 8bec mov ebp,esp 031800ab 83ec18 sub esp,18h 031800ae 833d142e920000 cmp dword ptr ds:[922E14h],0 031800b5 7405 je 031800bc 031800b7 e8c5a3f476 call mscorwks!JIT_DbgIsJustMyCode (7a0ca481) 031800bc 33d2 xor edx,edx 031800be 8955fc mov dword ptr [ebp-4],edx 031800c1 c745f800000000 mov dword ptr [ebp-8],0 031800c8 90 nop 031800c9 33d2 xor edx,edx 031800cb 8955fc mov dword ptr [ebp-4],edx 031800ce 90 nop 031800cf eb41 jmp 03180112 031800d1 90 nop 031800d2 b9382b3379 mov ecx,offset mscorlib_ni+0x272b38 (79332b38) (MT: System.Int32) 031800d7 e8401f79fd call 0091201c (JitHelp: CORINFO_HELP_NEWSFAST) 031800dc 8945f4 mov dword ptr [ebp-0Ch],eax 031800df 8b053020fb01 mov eax,dword ptr ds:[1FB2030h] ("Number: {0}") 031800e5 8945ec mov dword ptr [ebp-14h],eax 031800e8 8b45f4 mov eax,dword ptr [ebp-0Ch] 031800eb 8b55fc mov edx,dword ptr [ebp-4] 031800ee 895004 mov dword ptr [eax+4],edx 031800f1 8b45f4 mov eax,dword ptr [ebp-0Ch] 031800f4 8945e8 mov dword ptr [ebp-18h],eax 031800f7 8b4dec mov ecx,dword ptr [ebp-14h] 031800fa 8b55e8 mov edx,dword ptr [ebp-18h] 031800fd e8ee721376 call mscorlib_ni+0x1f73f0 (792b73f0) (System.String.Format(System.String, System.Object), mdToken: 060001bd) 03180102 8945f0 mov dword ptr [ebp-10h],eax 03180105 8b4df0 mov ecx,dword ptr [ebp-10h] 03180108 e8cb366176 call mscorlib_ni+0x6d37d8 (797937d8) (System.Console.WriteLine(System.String), mdToken: 060007c8) 0318010d 90 nop 0318010e 90 nop 0318010f ff45fc inc dword ptr [ebp-4] 03180112 837dfc0a cmp dword ptr [ebp-4],0Ah 03180116 0f9cc0 setl al 03180119 0fb6c0 movzx eax,al 0318011c 8945f8 mov dword ptr [ebp-8],eax 0318011f 837df800 cmp dword ptr [ebp-8],0 03180123 75ac jne 031800d1 <?XML:NAMESPACE PREFIX = SKYPE /> 03180125 90 nop 03180126 8be5 mov esp,ebp 03180128 5d pop ebp 03180129 c3 ret
当使用上述方式进行调试,而不是用 Visual Studio 进行调试时,正在查看的代码不再是编写的 CLI 语言,它已被优化成为CPU可以理解的东西
了解汇编语言的基础知识有助于进行此级别的调试。如下是一个控制台应用程序
try
{
AppSettingsReader asr = new AppSettingsReader();
string date = asr.GetValue("DateFormat", typeof(string)).ToString();
RunCommand(date);
}
catch (Exception)
{
//HA HA HA No one can hear you scream in here!!!!!!
}
debugger 下载后,安装目录如下所示
类型 | 目录 |
---|---|
安装位置 | C:\Program Files (x86)\Windows Kits\10 |
windbg.exe 位置 | C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe |
windbg.exe 位置 | C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe |
我们可以把目录 C:\Program Files (x86)\Windows Kits\10\Debuggers\x64
添加到环境变量中,这样子在新打开的 cmd 窗口中即可直接使用 cdb 或者 windbg 命令
使用SOS检查内存中的对象很简单。
命令 1:查找未被引用的对象
!DumpObject
命令 2:检测是否持有某个对象的引用
!GCRoot
命令 3:检查某个对象是否位于内存中
!DumpHeap
检查内存泄露的时候,需要明确两件事情:
DumpHeap 有 2 个参数:
-stat
:打开 debugger,并加载 SOS,查找所有的对象并根据类型分组展示,并根据内存总量降序排列-type
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。