金融公司网站开发,青岛网站建设q479185700棒,学校培训,wordpress 论坛主题在上一篇中我分析了CoreCLR中GC的内部处理#xff0c; 在这一篇我将使用LLDB实际跟踪CoreCLR中GC#xff0c;关于如何使用LLDB调试CoreCLR的介绍可以看:
微软官方的文档#xff0c;地址我在第3篇中的介绍#xff0c;地址LLDB官方的入门文档#xff0c;地址
源代码
本篇…在上一篇中我分析了CoreCLR中GC的内部处理 在这一篇我将使用LLDB实际跟踪CoreCLR中GC关于如何使用LLDB调试CoreCLR的介绍可以看:
微软官方的文档地址我在第3篇中的介绍地址LLDB官方的入门文档地址
源代码
本篇跟踪程序的源代码如下:
using System;using System.Runtime.InteropServices;namespace ConsoleApplication{ public class Program{ public class ClassA { } public class ClassB { } public class ClassC { } public static void Main(string[] args) { var a new ClassA();{ var b new ClassB(); } var c new ClassC();GCHandle handle GCHandle.Alloc(c, GCHandleType.Pinned);IntPtr address handle.AddrOfPinnedObject();Console.WriteLine((long)address);GC.Collect();Console.WriteLine(first collect completed);c null;GC.Collect();Console.WriteLine(second collect completed);GC.Collect();Console.WriteLine(third collect completed);}}
}
准备调试
环境和我的第三篇文章一样都是ubuntu 16.04 LTS首先需要发布程序:
dotnet publish
发布程序后把自己编译的coreclr文件覆盖到发布目录中: 复制coreclr/bin/Product/Linux.x64.Debug下的文件到程序目录/bin/Debug/netcoreapp1.1/ubuntu.16.04-x64/publish下。 请不要设置开启服务器GC一来是这篇文章分析的是工作站GC的处理二来开启服务器GC很容易导致调试时死锁。
进入调试
准备工作完成以后就可以进入调试了
cd 程序目录/bin/Debug/netcoreapp1.1/ubuntu.16.04-x64/publish
lldb-3.6 程序名称 首先设置gc主函数的断点然后运行程序
b gc1
r 我们停在了gc1函数现在可以用bt来看调用来源 这次是手动触发GC调用来源中包含了GCInterface::Collect和JIT生成的函数
需要显示当前的本地变量可以用fr v需要打印变量或者表达式可以用p 现在用n来步过用s来步进继续跟踪代码 进入标记阶段
在上图的位置中用s命令即可进入mark_phase继续步过到下图的位置 这时先让我们看下堆中的对象加载CoreCLR提供的LLDB插件
plugin load libsosplugin.so
插件提供的命令可以查看这里的文档
执行dumpheap查看堆中的状态 执行dso查看堆和寄存器中引用的对象 执行dumpobj查看对象的信息 在这一轮gc中对象a b c都会存活下来 可能你会对为什么b能存活下来感到惊讶对象b的引用分配在栈上即时生命周期过了也不一定会失效(rsp不会移回去)
br s -n Promote -c (long)*ppObject 0x00007fff5c01a2b8 # -n 名称 -c 条件c # 继续执行 接下来步进mark_object_simple函数然后步进gc_mark1函数 me re -s8 -c3 -fx o # 显示地址中的内存8个字节一组3组hex格式地址是op ((CObjectHeader*)o)-IsMarked() # 显示对象是否标记存活
我们可以清楚的看到标记对象存活设置了MethodTable的指针| 1
现在给PinObject下断点
br s -n PinObject -c (long)*pObjRef 0x00007fff5c01a1a0c 可以看到只是调用Promote然后传入GC_CALL_PINNED
继续步进到if (flags GC_CALL_PINNED)下的pin_object 可以看到pinned标记设置在同步索引块中
进入计划阶段
进入计划阶段后首先打印一下各个代的状态
p generation_table
使用这个命令可以看到gen 0 ~ gen 3的状态最后一个元素是空元素不用在意 继续步过下去到下图的这一段 在这里我们找到了一个plug的开始然后枚举已标记的对象下图是擦除marked和pinned标记的代码 在这里我们找到了一个plug的结束 如果是Full GC或者不升代在处理第一个plug之前就会设置gen 2的计划代边界 模拟压缩的地址 如果x越过原来的gen 0的边界设置gen 1的计划代边界(原gen 1的对象变gen 2) 如果不升代这里也会设置gen 0的计划代边界 模拟压缩后把原地址与压缩到的地址的偏移值存到plug信息(plug前的一块内存)中 构建plug树 设置brick表这个plug树跨了6个brick 如果升代模拟压缩全部完成后设置gen 0的计划代边界 接下来如果不动里面的变量将会进入清扫阶段(不满足进入压缩阶段的条件)
进入清扫阶段
这次为了观察对象c如何被清扫我们进入第二次gc的make_free_lists
b make_free_listsc
处理当前brick中的plug树 前面看到的对象c的地址是0x00007fff5c01a2e8这里我们就看对象c后面的plug是如何处理的
br s -f gc.cpp -l 23070 -c (long)tree 0x00007fff5c01a2e8c
我们可以看到plug 0x00007fff5c01a300前面的空余空间中包含了对象c空余空间的开始地址就是对象c 接下来就是在这片空余空间中创建free object和加到free list了 这里的大小不足( min_free_list)所以只会创建free object不会加到free list中 设置代边界之前计划阶段模拟的计划代边界不会被使用 清扫阶段完成后这次的gc的主要工作就完成了接下来让我们看重定位阶段和压缩阶段
进入重定位阶段
使用上面的程序让计划阶段选择压缩需要修改变量这里重新运行程序并使用以下命令
b gc.cpp:22489cexpr should_compact true n步过到下图的位置s步进到relocate_phase函数 到这个位置可以看到用了和标记阶段一样的GcScanRoots函数但是传入的不是Promote而是Relocate函数 接下来下断点进入Relocate函数
b Relocatec
GCHeap::Relocate函数不会重定位子对象只是用来重定位来源于根对象的引用 一直走到这个位置然后进入gc_heap::relocate_address函数 根据原地址和brick table找到对应的plug树 搜索plug树中old_address所属的plug 根据plug中的reloc修改指针地址 现在再来看relocate_survivors函数这个函数用于重定位存活下来的对象中的引用
b relocate_survivorsc 接下来会枚举并处理brick走到这里进入relocate_survivors_in_brick函数这个函数处理单个brick中的plug树 递归处理plug树种的各个节点 走到这里进入relocate_survivors_in_plug函数这个函数处理单个plug中的对象 图中的这个plug结尾被下一个plug覆盖过需要特殊处理这里继续进入relocate_shortened_survivor_helper函数 当前是unpinned plug下一个plug是pinned plug 枚举处理plug中的各个对象 如果这个对象结尾未被覆盖则调用relocate_obj_helper重定位对象中的各个成员 如果对象结尾被覆盖了则调用relocate_shortened_obj_helper重定位对象中的各个成员 在这里成员如果被覆盖会调用reloc_ref_in_shortened_obj修改备份数据中的成员但是因为go_through_object_nostart是一个macro这里无法调试内部的代码 接下来我们观察对象a的地址是否改变了
重新运行并修改should_compact变量
b gc.cpp:22489r
expr should_compact trueplugin load libsosplugin.so
dso
我们可以看到对象a的地址在0x00007fff5c01a2b8接下来给relocate_address函数下断点 br s -n relocate_address -c (long)(*pold_address) 0x00007fff5c01a2b8c 我们可以看到地址由0x00007fff5c01a2b8变成了0x00007fff5c0091b8 接下来一直跳回plan_phase下图可以看到重定位阶段完成以后新的地址上仍无对象重定位阶段只是修改了地址并未复制内存直到压缩阶段完成以后对象才会在新的地址 接下来看压缩阶段
进入压缩阶段
在重定位阶段完成以后走到下图的位置步进即可进入压缩阶段 枚举brick table 处理单个brick table中的plug树 根据下一个tree的gap计算last_plug的大小 处理单个plug中的对象 上面的last_plug是pinned plug所以不移动这里找了另外一个会移动的plug 下图可以看到整个plug都被复制到新的地址 这里再找一个结尾被覆盖过的plug看看是怎么处理的 首先把被覆盖的结尾大小加回去 然后把被覆盖的内容临时恢复回去 复制完再把覆盖的内容交换回来因为下一个plug还需要用 最终在recover_saved_pinned_info会全部恢复回去
参考链接
https://github.com/dotnet/coreclr/blob/master/Documentation/botr/garbage-collection.mdhttps://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/building/linux-instructions.mdhttps://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/building/debugging-instructions.mdhttp://lldb.llvm.org/tutorial.htmlhttp://lldb.llvm.org/lldb-gdb.html
写在最后
这一篇中我列出了几个gc中比较关键的部分但是还有成千上百处可以探讨的部分 如果你有兴趣可以自己试着用lldb调试CoreCLR可以学到很多文档和书籍之外的知识 特别是对于CoreCLR这种文档少注释也少的项目掌握调试工具可以大幅减少理解代码所需的时间
写完这一篇我将暂停研究GC下一篇开始会介绍JIT相关的内容敬请期待
相关文章
《代码的未来》读书笔记内存管理与GC那点事儿CoreCLR源码探索(一) Object是什么CoreCLR源码探索(二) new是什么CoreCLR源码探索(三) GC内存分配器的内部实现.NET跨平台之旅corehost 是如何加载 coreclr 的.NET CoreCLR开发人员指南(上)
原文地址http://www.cnblogs.com/zkweb/p/6626947.html.NET社区新闻深度好文微信中搜索dotNET跨平台或扫描二维码关注