golang mongo读取指定字段_深度剖析 Golang 的 GC 扫描对象的实现

本文深入剖析了 Go 语言的垃圾回收机制中的对象扫描过程,包括编译阶段的结构体对齐、运行时内存分配时的指针位标记,以及扫描阶段的具体实现。通过理解长度对齐和地址对齐的规则,以及 scanblock 和 scanobject 函数的工作原理,揭示了 Go 如何高效地进行垃圾回收扫描。
摘要由CSDN通过智能技术生成

  大纲  

  • 扫描的目的

  • 扫描的实现

    • 编译阶段

    • 运行期内存分配

    • 运行扫描阶段

  • 总结

7e47f97d3b30d170e4d41d305bbcba4f.png

之前阐述了 golang 垃圾回收通过保证三色不变式来保证回收的正确性,通过写屏障来实现业务赋值器和 gc 回收器正确的并发的逻辑。其中高概率的提到了“扫描队列”和“扫描对象”。队列这个逻辑非常容易理解,那么”扫描对象“ 这个你理解了吗?有直观的感受吗?这篇文章就是要把这个扫描的过程深入剖析下。

  1. 扫描的东西是啥?形象化描述下
  2. 怎么去做的扫描?形象化描述下

我们就是要把这两个抽象的概念搞懂,不能停留在语言级别浅层面,要知其然知其所以然。

  扫描的目的  

扫描到底是为了什么?

之前的文章我们深入剖析了垃圾回收的理论和实现,可以总结这么节点:

  1. 垃圾回收的根本目的是:“回收那些业务永远都不会再使用的内存块”;
  2. 扫描的目的则是:“把这些不再使用的内存块找出来”;

我们通过地毯式的扫描,从一些 root 起点开始,不断推进搜索,最终形成了一张有向可达的网,那些不在网里的就是没有被引用到的,也就是可回收的内存。

  扫描的实现  

扫描对象代码逻辑其实不简单,但主体线索很清晰,可以分为三部分:

  1. 编译阶段:编译期是非常重要的一环,针对静态类型做好标记准备(旁白:原则上编译期能做的绝对不留到运行期);
  2. 运行阶段:赋值器分配内存的时候,根据编译阶段的 type 标示,会为分配的对象内存设置好一个对应的指针标示的 bitmap;
  3. 扫描阶段:根据指针的 bitmap 标示,地毯式扫描;

编译阶段

结构体对齐

要理解编译阶段做的事情,那么首先要理解结构体对齐的基础知识。这个和 C 语言类似,golang 的结构体是有对齐规则的,也就是说,必要的时候可能会填充一些内存空间来满足对齐的要求。总结来说两条规则:

  1. 长度要对齐
  2. 地址要对齐
“长度要对齐”怎么理解?

结构体的长度要至少是内部最长的基础字段的整数倍。

举例:

type TestStruct struct {
      ptr uintptr     // 8   f1  uint32      // 4  f2  uint8       // 1}

这个结构体内存占用 size 多大?

答案是:16个字节,因为字段 ptr 是 uintptr 类型,占 8 字节,是内部字段最大的,TestStruct 整体长度要和 8 字节对齐。那么就是 16 字节了,而不是有些人想的 13 字节(8+4+1)。

dlv 调试如下:

(dlv) p typ*runtime._type {
    	size: 16,    ...

字节示意图:

|--8 Byte--|--4 Byte--|--4 Byte--|
“地址要对齐”怎么理解?

字段的地址偏移要是自身长度的整数倍。

举例:

type TestStruct struct {
      ptr uintptr   // 8  f1  uint8     // 1   f2  uint32    // 4}

假设 new 一个 TestStruct 结构体 a 的地址是 0xc00008a010 ,那么 &a.ptr 是 0xc00008a010 (= a + 0),&a.f1 是 0xc00008a018 (= a + 8) ,&a.f2 是 0xc00008a01c (= a + 8 + 4) 。

dlv 调试如下:

(dlv) p &a.ptr(*uintptr)(0xc00008a010)(dlv) p &a.f1(*uint8)(0xc00008a018)(dlv) p &a.f2(*uint32)(0xc00008a01c)

假设 TestStruct 分配对象 a 的地址是 0xc00008a010 ,解释如下:

  • ptr 是第一个字段,当然和结构体本身地址一样,相对偏移是 0,所以地址是 0xc00008a010 == 0xc00008a010 + 0
  • f1 是第二个字段,由于前一个字段 ptr 是 uintptr 类型(8字节),并且由于 f1 本身是 uint8 类型(1字节),所以 f1 从 8 偏移开始没毛病,所以 f1 的偏移地址从 0xc00008a018 == 0xc00008a010 + 8
  • f2 是第三个字段,由于前一个字段 f1 是 uint8(1字节),所以表面上看好像 f2 要接着 0xc00008a019 (= 0xc00008a018 +1) 这个地址才对,但是 f2 本身是 uint32 (4字节的类型),所以 f2 地址偏移至少要是 4 的倍数,所以 f2 的地址要从 0xc00008a01c (0xc00008a018 + 4)这个地址开始才对。也就是说,f1 到 f2 之间填充了一些不用的空间,为了地址对齐。

所以这样算下来,整个 TestStruct 的占用空间长度是 16字节 (8+1+3+4)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值