CoreCLR源码探索(五) GC内存收集器的内部实现 调试篇

在上一篇中我分析了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 == 0x00007fff5c01a1a0"c

可以看到只是调用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 > 0x00007fff5c01a2e8"c

我们可以看到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) == 0x00007fff5c01a2b8"c

我们可以看到地址由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.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调试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跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/326276.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小米路由器青春版装linux,比较费心的折腾 篇二:小米路由器青春版折腾负载均衡...

比较费心的折腾 篇二&#xff1a;小米路由器青春版折腾负载均衡2020-06-04 11:00:233点赞10收藏8评论创作立场声明&#xff1a;本文的核心目的是对于比较久远的硬件进行折腾&#xff0c;提及的软件可以在官网、软件包或者github找到。开篇预警本文截图较多&#xff0c;但是前期…

2019蓝桥杯省赛---java---C---6(旋转)

题目描述 问题描述图片旋转是对图片最简单的处理方式之一&#xff0c;在本题中&#xff0c;你需要对图片顺时 针旋转 90 度。 我们用一个 nm 的二维数组来表示一个图片&#xff0c;例如下面给出一个 34 的 图片的例子&#xff1a;1 3 5 7 9 8 7 6 3 5 9 7这个图片顺时针旋转 9…

linux container 原理,容器概念与Linux Container原理

一、容器与LxC在像KVM等众多主机虚拟化解决方案中&#xff0c;对每一个虚拟机实例提供的是从底层硬件开始一直到上层的环境&#xff0c;在硬件级进行资源划分。虚拟机的内核是运行在硬件内核之上的。由于每个虚拟实例都有自己的运行内核&#xff0c;所以各实例之间有非常好的隔…

微软建议Windows 10开发人员升级到Visual Studio 2017

既然Visual Studio 2017已经发布&#xff0c;那就意味着微软开始专注于让其成为默认开发平台。Creators Update SDK的发布&#xff08;面向即将到来的Windows 10 Creators Update&#xff09;就是这种转变的一个很好的例子。该SDK只有Visual Studio 2017支持。 幸运的是&#…

p2p linux 开源项目,权威开源项目(linux系统、sip、live555)

1、IT知识交流(语言工具、系统问题、开源项目交流)http://stackoverflow.com/tags2、linux系统www.kernel.orghttp://www.gnu.org/copyleft/lesser.html3、pjsuahttp://www.pjsip.org/1、freeswitch1、opensipshttp://opensips.org/pub/opensips/4、live555http://www.live555.…

在Visual Studio中使用任何C++编译器

原文发表时间: 3/07/2017原文发表地址: Use any C Compiler with Visual Studio 微软Visual Studio 2017支持几种C编译器以适应各种各样的代码库。除了很多人熟悉的微软Visual C编译器外&#xff0c; Visual Studio2017还支持Clang, GCC以及其他针对某些平台的编辑器。 这篇文…

2020蓝桥杯省赛---java---B---8(数字三角形)

题目描述 时间限制: 1.0s 内存限制: 512.0MB 本题总分&#xff1a;20 分【问题描述】上图给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。 对于每条路径&#xff0c;把路径上面的数加起来可以得到一个和&#xff0c;你的任务就是找到最 大的和。路径上的每一…

ASP.NET Core MVC 源码学习:Routing 路由

前言 最近打算抽时间看一下 ASP.NET Core MVC 的源码&#xff0c;特此把自己学习到的内容记录下来&#xff0c;也算是做个笔记吧。 路由作为 MVC 的基本部分&#xff0c;所以在学习 MVC 的其他源码之前还是先学习一下路由系统&#xff0c;ASP.NET Core 的路由系统相对于以前的…

linux netfilter 过滤数据包,Netfilter-iptabes报文过滤框架(一)

什么是Netfilter/iptableNetfilter/iptables是Linux内核内置的报文过滤框架&#xff0c;程序可以通过该框架完成报文过滤、地址转换(NAT)以及连接跟踪等功能。Netfilter/iptables由两部分组成&#xff0c;一部分是Netfilter的"钩子(hook)"&#xff0c;这些"钩子…

AI项目十九:YOLOV8实现目标追踪

若该文为原创文章&#xff0c;转载请注明原文出处。 主要是学习一下实现目标追踪的原理&#xff0c;并测试一下效果。 目的是通过YOLOV8实现人员检测&#xff0c;并实现人员追踪&#xff0c;没个人员给分配一个ID&#xff0c;实现追踪的效果。 也可以统计人数。在小区办公楼…

2020蓝桥杯省赛---java---B---7(单词分析)

题目描述 【问题描述】 小蓝正在学习一门神奇的语言&#xff0c;这门语言中的单词都是由小写英文字母组 成&#xff0c;有些单词很长&#xff0c;远远超过正常英文单词的长度。小蓝学了很长时间也记不 住一些单词&#xff0c;他准备不再完全记忆这些单词&#xff0c;而是根据单…

Dapper源码学习和源码修改(下篇)

继上篇Dapper源码学习和源码修改 讲了下自己学习Dapper的心得之后&#xff0c;下篇也随之而来&#xff0c;上篇主要讲的入参解析那下篇自然主打出参映射了。 好了&#xff0c;废话不多说&#xff0c;开始吧。 学习之前你的先学习怎么使用Dapper&#xff0c;这个我在上篇都提过…

2020蓝桥杯省赛---java---B---6(成绩分析)

题目描述 时间限制: 1.0s 内存限制: 512.0MB 本题总分&#xff1a;15 分【问题描述】 小蓝给学生们组织了一场考试&#xff0c;卷面总分为 100 分&#xff0c;每个学生的得分都是 一个 0 到 100 的整数。请计算这次考试的最高分、最低分和平均分。【输入格式】 输入的第一行包…

2020蓝桥杯省赛---java---B---5(排序)

题目描述 思路分析 01231391&#xff0c;而01231314105。 让下标为j的字符提到最前&#xff0c;正好达到100次交换&#xff0c;这样也满足了最小字典序&#xff0c;也可以验证一下。 代码实现 package TEST;public class Main {public static void main(String[] args) {St…

ASP.NET Core MVC 源码学习:MVC 启动流程详解

前言 在 上一篇 文章中&#xff0c;我们学习了 ASP.NET Core MVC 的路由模块&#xff0c;那么在本篇文章中&#xff0c;主要是对 ASP.NET Core MVC 启动流程的一个学习。 ASP.NET Core 是新一代的 ASP.NET 应用程序&#xff0c;它是跨平台的&#xff0c;并且不依赖于 IIS&…

2020蓝桥杯省赛---java---B---9(子串分值和)

题目描述 时间限制: 3.0s 内存限制: 512.0MB 本题总分&#xff1a;25 分【问题描述】 对于一个字符串 S&#xff0c;我们定义 S 的分值 f(S) 为 S 中出现的不同的字符个 数。例如 f(”aba”) 2&#xff0c;f(”abc”) 3, f(”aaa”) 1。 现在给定一个字符串 S[0…n−1]&…

GitHub 贡献第一的微软开源软件列表

作者&#xff5c;木环 编辑&#xff5c;小智 在GitHub上贡献最多的公司&#xff0c;不是Facebook&#xff0c;也不是Google&#xff0c;而是微软。InfoQ对微软数个较受社区欢迎的项目进行了整理&#xff0c;以飨读者。希望开源的精神&#xff0c;能给技术社区带来更多的实惠&am…

android 画布控件,Android canvas画图操作之切割画布实现方法(clipRect)

本文实例讲述了Android canvas画图操作之切割画布实现方法。分享给大家供大家参考&#xff0c;具体如下&#xff1a;android切割画布的历程不算很难&#xff0c;可是理解起来也比较麻烦&#xff0c;这里写一下我的理解 但是不一定正确&#xff1a;canvas.clipRect(30,30,70,Reg…

老司机实战Windows Server Docker:5 Windows Server Dockerfile葵花宝典

前面两篇&#xff08;简单运维1、简单运维2&#xff09;介绍了一些Windows Server Docker相关的基本运维知识。今天这一篇&#xff0c;Windows Server Dockerfile葵花宝典&#xff0c;涵盖了许多典型场景的Windows Server下的Dockerfile实例&#xff0c;并且每一个都包含可直接…

2020蓝桥杯省赛---java---B---2(寻找 2020)+测试txt

题目描述 text 0020000002202020002220002022002222202022020200022200020200222022002202202020020022200202000000002200222002022220222202220000222202200200202220200222200222202200000220220020202200022002200200200222000202220202002000000202200200220022020002022…