Golang中GC如何处理内部指针
Golang中GC如何处理内部指针 Golang的垃圾回收如何处理内部指针?考虑以下代码:
type Foo struct {
a,b,c,d int
bar Bar
e,f,g,h int
}
type Bar struct {
a,b,c,d int
}
a := &Foo{}
b := &a.bar
a = nil
// b 现在指向 `Foo` 的一小部分,但本身没有指向 `Foo` 的指针
在垃圾回收过程中,Go需要标记所有存活对象,并释放死亡对象。在上面的例子中,我们只能知道Bar是存活的,因为有一个指向它的指针。但是,我们不能释放它,因为我们从未单独分配它。我们只能释放Foo,但从技术上讲,没有指向Foo的指针。垃圾回收器如何知道b中的地址实际上来自对Foo的分配,而不是对Bar的分配?
我能想到一种可能的方法是:通过检查指针本身的位置,如果它属于一个用于96字节大小对象的区域,我们可以将其向下舍入到最接近的96的倍数以找到头部。但是,如果Bar本身是一个非常大的切片的一部分,就像这样:
a := make([]Foo, 1000000)
b := &a[1000].bar
a = nil
Golang如何知道b实际上来自a?分配本身的大小太大,无法放入用于固定大小对象的区域中。
更多关于Golang中GC如何处理内部指针的实战教程也可以访问 https://www.itying.com/category-94-b0.html
这是最简单的实现方式,但实际的源代码并非如此简单。如果你想深入了解,应该查看这部分源代码,这是最直接的方法。
更多关于Golang中GC如何处理内部指针的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
因为第一个 a 产生的数组切片会有一个内存区域,Go 的垃圾收集器会记录内存区域的范围。只要有一个指针指向该内存区域的任何部分,整个内存区域就会被标记为可达,并且不会被回收。
或许我之前说Go的垃圾回收器是基于内存区域范围而非特定变量对象来管理时,误导了你。只要存在指向内存区域任何部分的指针,该内存区域就会被标记为可达且不会被回收。 虽然我谈论的是变量对象,但其含义是指内存,不是抽象的对象,而是底层的内存。
例如,当指向0x1333的指针出现时,垃圾回收器将0x1234到0x2345的内存区域记录为活跃状态。 然而,在实践中,只需要记录变量对象之间的引用关系,就能理解何时释放的问题,这也是最直观的思考方式,尽管底层实现并非如此。
我刚刚查看了Go的运行时代码。它相当庞大,包含了许多细节。但如果我理解正确的话,他们似乎使用了某种两级基数树。指针的某些部分被用来在基数树中查找元素。这个元素是一个span元数据。这个span元数据包含了它们属于哪个页面的信息。一个span通常用于存储固定大小的对象,因此包含诸如大小类、元素数量等信息。大对象只是包含一个元素的span的一种特殊情况。我不太确定我的理解是否正确。
由于第一个
a产生的数组切片会拥有一个内存区域,Go 的垃圾收集器会记录内存区域的范围。只要存在指向该内存区域任何部分的指针,整个内存区域就会被标记为可达且不会被回收。
这是否意味着,对于大型对象,知道指针的头部地址不是 O(1) 操作?因为我们需要遍历所有已分配的对象才能确切知道哪个是头部。
func main() {
fmt.Println("hello world")
}
例如,当指向0x1333的指针出现时,GC将0x1234到0x2345记录为一个标记为活跃的内存区域。
是的,但是如何从0x1333得到0x1234呢?你是扫描整个已分配区域,并检查0x1333是否在它们之间吗?类似这样:
func markAddress(p uintptr) { // p = 0x1333
for _, memoryRegion := allMemoryRegionAllocated {
// 这个循环是O(N)的,其中N是存活内存区域的数量
if memoryRegion.start <= p && p < memoryRegion.end {
mark(memoryRegion)
}
}
}
我认为你应该阅读Go的实现文档,Go的垃圾回收机制通过标记变量对象来决定是否释放对象内存,运行时会检查上下文,引用变量对象,当存在未被标记的变量时,意味着该变量将被释放。
a := make([]Foo, 1000000)
b := &a[1000].bar
a = nil
ms := &runtime.MemStats{}
for {
fmt.Println(b.d) // b -> a so not free
// _ = b // nobody -> so a and b will free
time.Sleep(1 * time.Second)
runtime.GC()
runtime.ReadMemStats(ms)
fmt.Println(float64(ms.Alloc) / 1024 / 1024)
}
peakedshout:
Go语言的实现文档中提到,Go的垃圾回收器通过标记变量对象来决定是否释放对象内存。运行时会检查上下文,引用变量对象,当存在未被标记的变量时,意味着该变量…
Golang 如何知道 b 是从 a 分配而来的?
假设 a 的地址是 0x17700。b 的地址应该是 0x2ee20。现在,当垃圾回收器运行时,在标记阶段,只有一个根,即变量 b,其值为 0x2ee20。因此 Go 的垃圾回收器需要访问 0x2ee20。但是,Go 应该标记 0x17700,而不是 0x2ee20。Go 是如何知道 0x2ee20 实际上是 0x17700 的内部指针的呢?


