在那1篇小编将利用LLDB实际跟踪CoreCL中华V中GC,在上一篇中自作者分析了CoreCL冠道中GC的中间处理

在上壹篇中自小编分析了CoreCL奥迪Q3中GC的里边处理,
在这一篇小编将使用LLDB实际跟踪CoreCL瑞鹰中GC,关于如何采纳LLDB调节和测试CoreCLHaval的介绍能够看:

在上1篇中自身分析了CoreCLHaval中GC的中间处理,
在那1篇我将运用LLDB实际跟踪CoreCL奇骏中GC,关于如何使用LLDB调试CoreCLCRUISER的介绍能够看:

  • 微软官方的文书档案,地址
  • 本人在第1篇中的介绍,地址
  • LLDB官方的入门文书档案,地址
  • 微软官方的文书档案,地址
  • 本人在第三篇中的介绍,地址
  • 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");
        }
    }
}

源代码

本篇跟踪程序的源代码如下:

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");
        }
    }
}

准备调节和测试

条件和我的第三篇小说1样,都以ubuntu 1陆.04 LTS,首先必要揭露程序:

dotnet publish

发布程序后,把温馨编写翻译的coreclr文件覆盖到公布目录中:
复制coreclr/bin/Product/Linux.x64.Debug下的文书到程序目录/bin/Debug/netcoreapp1.1/ubuntu.16.04-x64/publish下。
请不要设置开启服务器GC,一来是那篇作品分析的是工作站GC的处理,2来开启服务器GC很轻松导致调节和测试时死锁。

预备调节和测试

条件和自作者的第贰篇小说一样,都以ubuntu 1六.0肆 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 程序名称

图片 1

首先设置gc主函数的断点,然后运维程序

b gc1
r

图片 2

咱俩停在了gc一函数,以后能够用bt来看调用来源

图片 3

这一次是手动触发GC,调用来源中包蕴了GCInterface::Collect和JIT生成的函数

亟待出示当前的本土变量能够用fr v,要求打字与印刷变量可能表达式能够用p

图片 4

现在用n来步过,用s来步进继续跟踪代码

图片 5

跻身调剂

准备干活形成以往就能够进来调剂了

cd 程序目录/bin/Debug/netcoreapp1.1/ubuntu.16.04-x64/publish
lldb-3.6 程序名称

图片 6

率先设置gc主函数的断点,然后运营程序

b gc1
r

图片 7

咱俩停在了gc一函数,以往能够用bt来看调用来源

图片 8

这一次是手动触发GC,调用来源中包含了GCInterface::Collect和JIT生成的函数

急需浮现当前的本土变量能够用fr v,须要打字与印刷变量恐怕表明式能够用p

图片 9

现在用n来步过,用s来步进继续跟踪代码

图片 10

进去标记阶段

在上海体育场所的地点中用s一声令下就能够进入mark_phase,继续步过到下图的地方

图片 11

此刻先让大家看下堆中的对象,加载CoreCLWrangler提供的LLDB插件

plugin load libsosplugin.so

插件提供的命令能够查看此地的文书档案

执行dumpheap翻开堆中的状态

图片 12
图片 13

执行dso翻看堆和寄存器中引用的对象

图片 14

执行dumpobj翻开对象的音讯

图片 15

在这壹轮gc中目的a b c都会存活下来,
恐怕你会对为啥b能存活下来感觉咋舌,对象b的引用分配在栈上,即时生命周期过了也不肯定会失灵(rsp不会移回去)

br s -n Promote -c "(long)*ppObject == 0x00007fff5c01a2b8" # -n 名称 -c 条件
c # 继续执行

图片 16

接下去步进mark_object_simple函数,然后步进gc_mark1函数

图片 17

me re -s8 -c3 -fx o # 显示地址中的内存,8个字节一组,3组,hex格式,地址是o
p ((CObjectHeader*)o)->IsMarked() # 显示对象是否标记存活

大家可以清楚的观看的志对象共处设置了MethodTable的指针|= 1

现在给PinObject下断点

br s -n PinObject -c "(long)*pObjRef == 0x00007fff5c01a1a0"
c

图片 18

能够阅览只是调用Promote然后传入GC_CALL_PINNED

持续步进到if (flags & GC_CALL_PINNED)下的pin_object

图片 19

能够见见pinned标记设置在同步索引块中

跻身标记阶段

在上海体育场地的义务中用s一声令下就能够进入mark_phase,继续步过到下图的职分

图片 20

那时候先让大家看下堆中的对象,加载CoreCLCRUISER提供的LLDB插件

plugin load libsosplugin.so

插件提供的吩咐能够查阅那里的文书档案

执行dumpheap翻开堆中的状态

图片 21
图片 22

执行dso查阅堆和寄存器中援引的对象

图片 23

执行dumpobj翻开对象的消息

图片 24

在那①轮gc中目的a b c都会存活下来,
或然您会对怎么b能存活下来感觉愕然,对象b的引用分配在栈上,即时生命周期过了也不肯定会失效(rsp不会移回去)

br s -n Promote -c "(long)*ppObject == 0x00007fff5c01a2b8" # -n 名称 -c 条件
c # 继续执行

图片 25

接下去步进mark_object_simple函数,然后步进gc_mark1函数

图片 26

me re -s8 -c3 -fx o # 显示地址中的内存,8个字节一组,3组,hex格式,地址是o
p ((CObjectHeader*)o)->IsMarked() # 显示对象是否标记存活

作者们得以知晓的见到标志对象共处设置了MethodTable的指针|= 1

现在给PinObject下断点

br s -n PinObject -c "(long)*pObjRef == 0x00007fff5c01a1a0"
c

图片 27

能够看出只是调用Promote然后传入GC_CALL_PINNED

后续步进到if (flags & GC_CALL_PINNED)下的pin_object

图片 28

能够见到pinned标记设置在同步索引块中

跻身布置阶段

进去安插阶段后率先打字与印刷一下梯次代的图景

p generation_table

选用这么些命令能够看出gen 0 ~ gen 3的事态,最终一个成分是空成分不用在意

图片 29

后续步过下去到下图的那一段

图片 30

在此处我们找到了1个plug的起首,然后枚举已标记的指标,下图是擦除marked和pinned标记的代码

图片 31

在此处大家找到了一个plug的扫尾

图片 32

假如是Full GC可能不升代,在拍卖首个plug此前就会设置gen 2的陈设代边界

图片 33

照猫画虎压缩的地点

图片 34

只要x越过原来的gen 0的边际,设置gen 壹的安插代边界(原gen 一的指标变gen
②),
设若不升代那里也会设置gen 0的安排代边界

图片 35

依傍压缩后把原地方与削减到的地点的偏移值存到plug音信(plug前的一块内部存储器)中

图片 36

构建plug树

图片 37

设置brick表,这个plug树跨了6个brick

图片 38
图片 39

万壹升代,模拟压缩全体完事后安装gen 0的布署代边界

图片 40

接下去如若不动里面包车型大巴变量,将会进去清扫阶段(不满足进入削减阶段的原则)

跻身布置阶段

进去布署阶段后首先打字与印刷一下各类代的场地

p generation_table

利用这些命令能够看来gen 0 ~ gen 三的图景,最终一个成分是空成分不用在意

图片 41

继续步过下去到下图的这一段

图片 42

在那里大家找到了3个plug的先河,然后枚举已标记的对象,下图是擦除marked和pinned标记的代码

图片 43

在那里大家找到了贰个plug的扫尾

图片 44

若是是Full GC只怕不升代,在拍卖第1个plug在此以前就会设置gen 2的安排代边界

图片 45

效仿压缩的地点

图片 46

若果x越过原来的gen 0的界线,设置gen 1的布署代边界(原gen 一的对象变gen
二),
若是不升代那里也会设置gen 0的陈设代边界

图片 47

模仿压缩后把原地方与削减到的地址的偏移值存到plug消息(plug前的一块内部存款和储蓄器)中

图片 48

构建plug树

图片 49

设置brick表,这个plug树跨了6个brick

图片 50
图片 51

比方升代,模拟压缩全部做到后装置gen 0的安顿代边界

图片 52

接下去如若不动里面包车型地铁变量,将会跻身清扫阶段(不满意进入削减阶段的规则)

进入清扫阶段

此次为了考查对象c怎么着被清扫,大家进来第3回gc的make_free_lists

b make_free_lists
c

拍卖当下brick中的plug树

图片 53

前边看到的对象c的地点是0x00007fff5c0一a二e八,那里大家就看对象c前边的plug是什么样处理的

br s -f gc.cpp -l 23070 -c "(long)tree > 0x00007fff5c01a2e8"
c

大家可以看看plug
0x0000柒fff5c01a300眼前的悠闲空间中隐含了目的c,空余空间的开端地址正是指标c

图片 54

接下去正是在那片空余空间中开创free object和加到free list了,
那边的高低不足(< min_free_list)所以只会创设free object不会加到free
list中

图片 55

安装代边界,从前计划阶段模拟的安顿代边界不会被选择

图片 56

清扫阶段完毕后此番的gc的重要办事就做到了,接下去让大家侧重稳定阶段和削减阶段

进去清扫阶段

本次为了侦察对象c怎么样被清扫,大家进来第贰遍gc的make_free_lists

b make_free_lists
c

拍卖当下brick中的plug树

图片 57

前面看到的对象c的地址是0x00007fff5c0一a2e捌,这里大家就看对象c前面包车型地铁plug是什么样处理的

br s -f gc.cpp -l 23070 -c "(long)tree > 0x00007fff5c01a2e8"
c

大家能够见见plug
0x00007fff⑤c0一a300前面的悠闲空间中带有了对象c,空余空间的上马地址就是目标c

图片 58

接下去正是在那片空余空间中开创free object和加到free list了,
此间的尺寸不足(< min_free_list)所以只会创制free object不会加到free
list中

图片 59

安装代边界,从前安排阶段模拟的安顿代边界不会被利用

图片 60

清扫阶段达成后这一次的gc的要害办事就做到了,接下去让大家珍视稳定阶段和减弱阶段

跻身重向来阶段

动用方面包车型地铁顺序让陈设阶段采用压缩,要求修改动量,那里再一次运转程序并应用以下命令

b gc.cpp:22489
c
expr should_compact = true

图片 61

n步过到下图的职责,s步进到relocate_phase函数

图片 62

到那几个岗位能够见见用了和标记阶段1样的GcScanRoots函数,可是传入的不是Promote而是Relocate函数

图片 63

接下去下断点进入Relocate函数

b Relocate
c

GCHeap::Relocate函数不会重定位子对象,只是用来重一直来源于根对象的引用

图片 64

平素走到这一个职位然后进入gc_heap::relocate_address函数

图片 65

据书上说原地点和brick table找到相应的plug树

图片 66

搜索plug树中old_address所属的plug

图片 67

基于plug中的reloc修改指针地址

图片 68

近期再来看relocate_sur三星rs函数,那一个函数用于重一直存活下来的目的中的引用

b relocate_survivors
c

图片 69

接下去会枚举并拍卖brick,走到这里进入relocate_survivors_in_brick函数,那些函数处理单个brick中的plug树

图片 70

递归处理plug树种的各种节点

图片 71

走到那里进入relocate_survivors_in_plug函数,这么些函数处理单个plug中的对象

图片 72

图中的这一个plug结尾被下一个plug覆盖过,需求越发处理,这里延续进入relocate_shortened_survivor_helper函数

图片 73

当前是unpinned plug,下一个plug是pinned plug

图片 74

枚举处理plug中的各类对象

图片 75

假定这几个目的结尾未被掩盖,则调用relocate_obj_helper重平昔指标中的各类成员

图片 76
图片 77

一旦指标结尾被遮住了,则调用relocate_shortened_obj_helper重一向指标中的各样成员
在此处成员假如被掩盖会调用reloc_ref_in_shortened_obj修改备份数据中的成员,但是因为go_through_object_nostart是一个macro这里不只怕调节和测试内部的代码

图片 78

接下去大家着眼对象a的地点是还是不是退换了

双重运营并修改should_compact变量

b gc.cpp:22489
r
expr should_compact = true
plugin load libsosplugin.so
dso

咱俩得以看来目的a的地点在0x00007fff伍c0一a二b八,接下去给relocate_address函数下断点

图片 79

br s -n relocate_address -c "(long)(*pold_address) == 0x00007fff5c01a2b8"
c

图片 80

大家得以看出地点由0x0000七fff伍c01a二b捌变为了0x0000七fff五c00玖一b8

图片 81

接下去平昔跳回plan_phase,下图能够看出重一贯阶段完结之后新的地方上仍无对象,重平昔阶段只是修改了地点并未有复制内部存款和储蓄器,直到压缩阶段达成未来对象才会在新的地址

图片 82

接下去看压缩阶段

进入重平素阶段

应用方面包车型大巴先后让安排阶段采取压缩,需求修改换量,那里再度运行程序并运用以下命令

b gc.cpp:22489
c
expr should_compact = true

图片 83

n步过到下图的职位,s步进到relocate_phase函数

图片 84

到这些职位能够见见用了和符号阶段1样的GcScanRoots函数,可是传入的不是Promote而是Relocate函数

图片 85

接下去下断点进入Relocate函数

b Relocate
c

GCHeap::Relocate函数不会重定位子对象,只是用来重一直来源于根对象的引用

图片 86

直接走到那一个岗位然后进入gc_heap::relocate_address函数

图片 87

依照原地方和brick table找到相应的plug树

图片 88

搜索plug树中old_address所属的plug

图片 89

听别人讲plug中的reloc修改指针地址

图片 90

明天再来看relocate_survivors函数,那么些函数用于重平素存活下来的指标中的引用

b relocate_survivors
c

图片 91

接下去会枚举并处理brick,走到此地进入relocate_survivors_in_brick函数,那些函数处理单个brick中的plug树

图片 92

递归处理plug树种的壹壹节点

图片 93

走到此处进入relocate_survivors_in_plug函数,那几个函数处理单个plug中的对象

图片 94

图中的这么些plug结尾被下1个plug覆盖过,必要特殊处理,那里继续进入relocate_shortened_survivor_helper函数

图片 95

当前是unpinned plug,下一个plug是pinned plug

图片 96

枚举处理plug中的各样对象

图片 97

只要那几个指标结尾未被遮住,则调用relocate_obj_helper重一向指标中的各样成员

图片 98
图片 99

倘诺指标结尾被掩盖了,则调用relocate_shortened_obj_helper重一直指标中的种种成员
在此处成员假诺被覆盖会调用reloc_ref_in_shortened_obj修改备份数据中的成员,可是因为go_through_object_nostart是一个macro这里不可能调节和测试内部的代码

图片 100

接下去大家阅览对象a的地点是还是不是变动了

再也运维并修改should_compact变量

b gc.cpp:22489
r
expr should_compact = true
plugin load libsosplugin.so
dso

咱俩得以看来目的a的地点在0x00007fff5c01a二b八,接下去给relocate_address函数下断点

图片 101

br s -n relocate_address -c "(long)(*pold_address) == 0x00007fff5c01a2b8"
c

图片 102

大家得以看出地方由0x00007fff伍c0一a2b八变为了0x0000七fff伍c00九1b八

图片 103

接下去一直跳回plan_phase,下图能够看来重平素阶段完毕以往新的位置上仍无对象,重一直阶段只是修改了地址并未有复制内部存款和储蓄器,直到压缩阶段完毕现在对象才会在新的地点

图片 104

接下去看压缩阶段

跻身削减阶段

在重一向阶段实现之后走到下图的义务,步进就能够进入削减阶段

图片 105

枚举brick table

图片 106

拍卖单个brick table中的plug树

图片 107

遵照下1个tree的gap计算last_plug的大小

图片 108

拍卖单个plug中的对象

图片 109

上面的last_plug是pinned plug所以不运动,这里找了其它2个会移动的plug

图片 110

下图能够看到整个plug都被复制到新的地点

图片 111

此间再找二个末尾被掩盖过的plug看看是怎么处理的

图片 112

首先把被遮盖的最终大小加回去

图片 113

接下来把被遮住的始末权且苏醒回去

图片 114
图片 115

复制完再把覆盖的始末沟通回来,因为下八个plug还供给用

图片 116

最终在recover_saved_pinned_info会全体上涨回去

进去削减阶段

在重一向阶段完结今后走到下图的职责,步进就能够进入削减阶段

图片 117

枚举brick table

图片 118

处理单个brick table中的plug树

图片 119

听他们讲下2个tree的gap计算last_plug的大小

图片 120

拍卖单个plug中的对象

图片 121

上面的last_plug是pinned plug所以不运动,那里找了其余三个会活动的plug

图片 122

下图可以看到全数plug都被复制到新的地址

图片 123

此间再找一个最后被遮盖过的plug看看是怎么处理的

图片 124

首先把被遮盖的末尾大小加回去

图片 125

接下来把被遮住的始末近来苏醒回去

图片 126
图片 127

复制完再把覆盖的始末调换回来,因为下四个plug还须求用

图片 128

最终在recover_saved_pinned_info会全体回涨回去

参考链接

https://github.com/dotnet/coreclr/blob/master/Documentation/botr/garbage-collection.md
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/building/linux-instructions.md
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/building/debugging-instructions.md
http://lldb.llvm.org/tutorial.html
http://lldb.llvm.org/lldb-gdb.html

参考链接

https://github.com/dotnet/coreclr/blob/master/Documentation/botr/garbage-collection.md
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/building/linux-instructions.md
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/building/debugging-instructions.md
http://lldb.llvm.org/tutorial.html
http://lldb.llvm.org/lldb-gdb.html

写在最后

那1篇中本身列出了多少个gc中相比主要的有的,不过还有成千上百处能够探求的局地,
万一您风乐趣能够协调节和测试着用lldb调节和测试CoreCL劲客,能够学到很多文书档案和书本之外的学问,
尤其是对此CoreCL翼虎那种文书档案少注释也少的项目,精晓调节和测试工具得以大幅削减了解代码所需的光阴

写完那1篇我将中断商量GC,下一篇开头会介绍JIT相关的内容,敬请期待

写在结尾

那一篇中自己列出了多少个gc中相比重大的片段,可是还有成千上百处能够追究的一部分,
如若您风趣味能够友善试着用lldb调节和测试CoreCL途观,能够学到很多文书档案和本本之外的文化,
专门是对于CoreCL奥德赛那种文书档案少注释也少的类型,通晓调节和测试工具得以大幅回落精晓代码所需的小运

写完那一篇笔者将中止商讨GC,下一篇初叶会介绍JIT相关的剧情,敬请期待