在上一篇中小编分析了CoreCL纳瓦拉中GC的中间处理,在那一篇笔者将运用LLDB实际跟踪CoreCLQashqai中GCbetvictor1946

在上壹篇中自个儿分析了CoreCL猎豹CS陆中GC的内处,
在那壹篇作者将选用LLDB实际跟踪CoreCLPRADO中GC,关于什么利用LLDB调试CoreCLOdyssey的介绍能够看:

在上1篇中小编分析了CoreCLOdyssey中GC的在那之中处理,
在那1篇作者将使用LLDB实际跟踪CoreCL大切诺基中GC,关于什么接纳LLDB调试CoreCL翼虎的牵线能够看:

  • 微软官方的文书档案,地址
  • 作者在第3篇中的介绍,地址
  • LLDB官方的入门文书档案,地址
  • 微软官方的文书档案,地址
  • 自作者在第2篇中的介绍,地址
  • 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陆.0肆 LTS,首先须要发表程序:

dotnet publish

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

桑土绸缪调节和测试

环境和自个儿的第3篇小说一样,都以ubuntu 16.0四 LTS,首先须要宣布程序:

dotnet publish

发布程序后,把团结编写翻译的coreclr文件覆盖到发表目录中:
复制coreclr/bin/Product/Linux.x64.Debug下的文件到程序目录/bin/Debug/netcoreapp1.1/ubuntu.16.04-x64/publish下。
请不要设置开启服务器GC,1来是那篇作品分析的是工作站GC的拍卖,贰来开启服务器GC很简单导致调节和测试时死锁。

进去调节和测试

未雨绸缪工作到位之后就足以进入调试了

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

betvictor1946 1

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

b gc1
r

betvictor1946 2

笔者们停在了gc一函数,未来能够用bt来看调用来源

betvictor1946 3

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

必要显示当前的地面变量能够用fr v,需求打字与印刷变量只怕表明式能够用p

betvictor1946 4

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

betvictor1946 5

进去调剂

防微杜渐干活达成以往就能够进入调剂了

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

betvictor1946 6

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

b gc1
r

betvictor1946 7

咱俩停在了gc1函数,今后得以用bt来看调用来源

betvictor1946 8

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

亟需出示当前的本土变量能够用fr v,必要打字与印刷变量大概表明式能够用p

betvictor1946 9

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

betvictor1946 10

进去标记阶段

在上海图书馆的岗位中用s指令即可进入mark_phase,继续步过到下图的岗位

betvictor1946 11

此时先让大家看下堆中的对象,加载CoreCL奥迪Q伍提供的LLDB插件

plugin load libsosplugin.so

插件提供的一声令下可以查看此处的文书档案

执行dumpheap翻开堆中的状态

betvictor1946 12
betvictor1946 13

执行dso查阅堆和寄存器中引用的靶子

betvictor1946 14

执行dumpobj翻开对象的音信

betvictor1946 15

在这1轮gc中指标a b c都会存活下来,
或许你会对为啥b能存活下来感觉好奇,对象b的引用分配在栈上,即时生命周期过了也不肯定会失灵(rsp不会移回去)

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

betvictor1946 16

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

betvictor1946 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

betvictor1946 18

能够看出只是调用Promote接下来传入GC_CALL_PINNED

再三再四步进到if (flags & GC_CALL_PINNED)下的pin_object

betvictor1946 19

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

进入标记阶段

在上图的职位中用s命令即可进入mark_phase,继续步过到下图的职位

betvictor1946 20

那时候先让我们看下堆中的对象,加载CoreCLRubicon提供的LLDB插件

plugin load libsosplugin.so

插件提供的吩咐能够查看那里的文档

执行dumpheap翻看堆中的状态

betvictor1946 21
betvictor1946 22

执行dso查看堆和寄存器中引用的目的

betvictor1946 23

执行dumpobj翻看对象的音讯

betvictor1946 24

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

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

betvictor1946 25

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

betvictor1946 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

betvictor1946 27

能够见到只是调用Promote下一场传入GC_CALL_PINNED

继承步进到if (flags & GC_CALL_PINNED)下的pin_object

betvictor1946 28

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

跻身布署阶段

进去陈设阶段后第三打字与印刷一下各种代的事态

p generation_table

运用那些命令能够观察gen 0 ~ gen 三的场地,最终2个要素是空成分不用在意

betvictor1946 29

一连步过下去到下图的那一段

betvictor1946 30

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

betvictor1946 31

在此地我们找到了1个plug的终结

betvictor1946 32

一经是Full GC或然不升代,在拍卖第二个plug以前就会设置gen 二的陈设代边界

betvictor1946 33

优孟衣冠压缩的地点

betvictor1946 34

设若x越过原来的gen 0的界线,设置gen 1的安插代边界(原gen 壹的对象变gen
贰),
假定不升代那里也会安装gen 0的布署代边界

betvictor1946 35

宪章压缩后把原地点与裁减到的地址的偏移值存到plug信息(plug前的一块内部存储器)中

betvictor1946 36

构建plug树

betvictor1946 37

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

betvictor1946 38
betvictor1946 39

若果升代,模拟压缩全体成就后安装gen 0的陈设代边界

betvictor1946 40

接下去假如不动里面包车型大巴变量,将会进去清扫阶段(不满意进入削减阶段的基准)

跻身布置阶段

进去布置阶段后先是打字与印刷一下种种代的意况

p generation_table

应用那么些命令能够看到gen 0 ~ gen 三的景观,最终1个要素是空成分不用在意

betvictor1946 41

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

betvictor1946 42

在此间大家找到了四个plug的初叶,然后枚举已标记的靶子,下图是擦除marked和pinned标记的代码

betvictor1946 43

在此地大家找到了1个plug的完毕

betvictor1946 44

假设是Full GC可能不升代,在处理第多少个plug从前就会安装gen 贰的布署代边界

betvictor1946 45

仿照压缩的地点

betvictor1946 46

一经x越过原来的gen 0的疆界,设置gen 一的安排代边界(原gen 1的对象变gen
二),
要是不升代那里也会设置gen 0的安排代边界

betvictor1946 47

模仿压缩后把原地方与减少到的地址的偏移值存到plug音信(plug前的壹块内部存款和储蓄器)中

betvictor1946 48

构建plug树

betvictor1946 49

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

betvictor1946 50
betvictor1946 51

倘诺升代,模拟压缩全体成功后装置gen 0的安排代边界

betvictor1946 52

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

跻身清扫阶段

这一次为了侦察对象c怎么样被清扫,大家进去第一回gc的make_free_lists

b make_free_lists
c

拍卖当下brick中的plug树

betvictor1946 53

前方看到的指标c的地址是0x0000柒fff伍c0一a2e八,那里大家就看对象c前面包车型大巴plug是何等处理的

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

大家能够见见plug
0x00007fff伍c0一a300前方的闲暇空间中涵盖了指标c,空余空间的伊始地址正是指标c

betvictor1946 54

接下去就是在那片空余空间中创设free object和加到free list了,
此处的大小不足(< min_free_list)所以只会成立free object不会加到free
list中

betvictor1946 55

安装代边界,从前布置阶段模拟的陈设代边界不会被利用

betvictor1946 56

清扫阶段达成后此次的gc的首要性办事就做到了,接下去让大家重视稳定阶段和减弱阶段

跻身清扫阶段

本次为了调查对象c怎么样被清扫,大家进去第3遍gc的make_free_lists

b make_free_lists
c

处理当下brick中的plug树

betvictor1946 57

最近看到的指标c的地点是0x0000柒fff伍c0壹a二e八,那里大家就看对象c后边的plug是怎么样处理的

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

咱俩得以看来plug
0x00007fff5c01a300日前的空闲空间中包罗了对象c,空余空间的启幕地址就是目标c

betvictor1946 58

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

betvictor1946 59

安装代边界,从前安排阶段模拟的陈设代边界不会被采取

betvictor1946 60

清扫阶段完成后本次的gc的关键工作就完了了,接下去让我们讲究稳定阶段和压缩阶段

进入重一贯阶段

动用方面包车型客车顺序让安排阶段选用压缩,须求修改变量,那里再次运转程序并使用以下命令

b gc.cpp:22489
c
expr should_compact = true

betvictor1946 61

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

betvictor1946 62

到那些地方可以见见用了和符号阶段一样的GcScanRoots函数,不过传入的不是Promote而是Relocate函数

betvictor1946 63

接下去下断点进入Relocate函数

b Relocate
c

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

betvictor1946 64

直接走到这些任务然后进入gc_heap::relocate_address函数

betvictor1946 65

依据原地点和brick table找到相应的plug树

betvictor1946 66

搜索plug树中old_address所属的plug

betvictor1946 67

据说plug中的reloc修改指针地址

betvictor1946 68

方今再来看relocate_sur小米rs函数,这一个函数用于重一贯存活下来的目的中的引用

b relocate_survivors
c

betvictor1946 69

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

betvictor1946 70

递归处理plug树种的逐条节点

betvictor1946 71

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

betvictor1946 72

图中的这一个plug结尾被下三个plug覆盖过,必要新鲜处理,这里三番五次进入relocate_shortened_survivor_helper函数

betvictor1946 73

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

betvictor1946 74

枚举处理plug中的各类对象

betvictor1946 75

比方那个指标结尾未被遮盖,则调用relocate_obj_helper重一直目的中的种种成员

betvictor1946 76
betvictor1946 77

只要目的结尾被掩盖了,则调用relocate_shortened_obj_helper重一直目的中的各样成员
在此间成员要是被掩盖会调用reloc_ref_in_shortened_obj修改备份数据中的成员,不过因为go_through_object_nostart是三个macro那里不或者调节和测试内部的代码

betvictor1946 78

接下去大家着眼对象a的地方是或不是改变了

再度运转并修改should_compact变量

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

笔者们能够看来目的a的地点在0x00007fff伍c0一a2b8,接下去给relocate_address函数下断点

betvictor1946 79

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

betvictor1946 80

咱俩得以看出地点由0x0000柒fff5c0一a2b8改成了0x0000七fff5c00九一b捌

betvictor1946 81

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

betvictor1946 82

接下去看压缩阶段

跻身重平昔阶段

使用方面包车型地铁顺序让安插阶段选择压缩,供给修改变量,这里再度运维程序并利用以下命令

b gc.cpp:22489
c
expr should_compact = true

betvictor1946 83

n步过到下图的地点,s步进到relocate_phase函数

betvictor1946 84

到那一个职责能够看看用了和符号阶段1样的GcScanRoots函数,然则传入的不是Promote而是Relocate函数

betvictor1946 85

接下去下断点进入Relocate函数

b Relocate
c

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

betvictor1946 86

直接走到这几个地点然后进入gc_heap::relocate_address函数

betvictor1946 87

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

betvictor1946 88

搜索plug树中old_address所属的plug

betvictor1946 89

依据plug中的reloc修改指针地址

betvictor1946 90

如今再来看relocate_sur黑莓rs函数,这些函数用于重平昔存活下来的目的中的引用

b relocate_survivors
c

betvictor1946 91

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

betvictor1946 92

递归处理plug树种的次第节点

betvictor1946 93

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

betvictor1946 94

图中的这么些plug结尾被下一个plug覆盖过,需求特殊处理,那里接二连三进入relocate_shortened_survivor_helper函数

betvictor1946 95

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

betvictor1946 96

枚举处理plug中的各样对象

betvictor1946 97

只要这一个指标结尾未被掩盖,则调用relocate_obj_helper重一向目的中的各类成员

betvictor1946 98
betvictor1946 99

设若指标结尾被覆盖了,则调用relocate_shortened_obj_helper重一向目的中的种种成员
在此地成员尽管被覆盖会调用reloc_ref_in_shortened_obj修改备份数据中的成员,不过因为go_through_object_nostart是2个macro那里不大概调节和测试内部的代码

betvictor1946 100

接下去大家观看对象a的地点是不是变动了

再次运转并修改should_compact变量

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

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

betvictor1946 101

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

betvictor1946 102

大家可以观望地点由0x00007fff伍c01a二b8化为了0x0000七fff5c00玖1b8

betvictor1946 103

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

betvictor1946 104

接下去看压缩阶段

进去削减阶段

在重一直阶段实现之后走到下图的地方,步进即可进入削减阶段

betvictor1946 105

枚举brick table

betvictor1946 106

拍卖单个brick table中的plug树

betvictor1946 107

基于下二个tree的gap总括last_plug的大小

betvictor1946 108

处理单个plug中的对象

betvictor1946 109

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

betvictor1946 110

下图可以见到整个plug都被复制到新的地方

betvictor1946 111

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

betvictor1946 112

先是把被遮住的最终大小加回去

betvictor1946 113

接下来把被覆盖的剧情一时恢复生机回去

betvictor1946 114
betvictor1946 115

复制完再把覆盖的情节调换回来,因为下1个plug还亟需用

betvictor1946 116

最终在recover_saved_pinned_info会全部重操旧业回去

进入削减阶段

在重一向阶段达成现在走到下图的地点,步进即可进入削减阶段

betvictor1946 117

枚举brick table

betvictor1946 118

处理单个brick table中的plug树

betvictor1946 119

依照下多个tree的gap计算last_plug的大小

betvictor1946 120

处理单个plug中的对象

betvictor1946 121

上面的last_plug是pinned plug所以不挪窝,那里找了其余二个会移动的plug

betvictor1946 122

下图能够看看全数plug都被复制到新的地址

betvictor1946 123

此处再找多个末尾被遮盖过的plug看看是怎么处理的

betvictor1946 124

先是把被遮住的尾声大小加回去

betvictor1946 125

然后把被掩盖的内容目前恢复生机回去

betvictor1946 126
betvictor1946 127

复制完再把覆盖的内容调换回来,因为下二个plug还要求用

betvictor1946 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

写在终极

那一篇中自笔者列出了多少个gc中相比根本的局地,可是还有成千上百处能够追究的片段,
比方您有趣味可以协调节和测试着用lldb调节和测试CoreCL奥迪Q5,能够学到很多文书档案和书本之外的学问,
特意是对于CoreCL帕杰罗那种文书档案少注释也少的品类,通晓调节和测试工具得以大幅减小掌握代码所需的时日

写完那一篇作者将刹车商讨GC,下一篇开首会介绍JIT相关的始末,敬请期待

写在最后

那1篇中自身列出了多少个gc中相比关键的部分,可是还有成千上百处能够追究的有个别,
若果您有趣味能够友善试着用lldb调节和测试CoreCL宝马X3,能够学到很多文书档案和图书之外的学识,
尤其是对于CoreCL宝马X3那种文书档案少注释也少的项目,明白调节和测试工具得以急剧削减精通代码所需的时日

写完那1篇笔者将暂停商讨GC,下1篇起始会介绍JIT相关的始末,敬请期待

相关文章