Go语言实践Go

go-内存机制(5)

2020-03-29  本文已影响0人  GGBond_8488

总结

Go的内存结构

每个Go程序进程都由操作系统(OS)预先分配了一些虚拟内存,这是该进程可以访问的全部内存。在这个虚拟内存中实际正在使用的内存称为Resident Set(驻留内存)。该空间由内部内存结构管理,如下所示:


mheap(堆)

这里是Go存储动态数据(在编译时无法计算大小的任何数据)的地方。它是最大的内存块,也是进行垃圾收集(GC)的地方
驻留内存(resident set)被划分为每个大小为8KB的页,并由一个全局mheap对象管理。

大对象(大小> 32kb的对象)直接从mheap分配。这些大对象申请请求是以获取中央锁(central lock)为代价的,因此在任何给定时间点只能满足一个P的请求。

mheap通过将页归类为不同结构进行管理的:

stack(栈)

栈存储区,每个Goroutine(G)有一个栈。在这里存储了静态数据,包括函数栈帧,静态结构,原生类型值和指向动态结构的指针。

与许多垃圾回收语言相比,Go的一个主要区别是许多对象直接在程序栈上分配。Go编译器使用一种称为"逃逸分析"的过程来查找其生命周期在编译时已知的对象,并将它们分配在栈上,而不是在垃圾回收的堆内存中。在编译过程中,Go进行了逃逸分析,以确定哪些可以放入栈(静态数据),哪些需要放入堆(动态数据)。我们可以通过运行带有-gcflags '-m'标志的go build命令来查看分析的细节

  • main函数被保存栈中的“main栈帧”中
  • 每个函数调用都作为一个栈帧块被添加到堆中
  • 包括参数和返回值在内的所有静态变量都保存在函数的栈帧块内
  • 无论类型如何,所有静态值都直接存储在栈中。这也适用于全局范畴
  • 所有动态类型都在堆上创建,并且被栈上的指针所引用。小于32Kb的对象由P的mcache分配。这同样适用于全局范畴
  • 具有静态数据的结构体保留在栈上,直到在该位置将任何动态值添加到该结构中为止。该结构被移到堆上。
  • 从当前函数调用的函数被推入堆顶部
  • 当函数返回时,其栈帧将从栈中删除
  • 一旦主过程(main)完成,堆上的对象将不再具有来自Stack的指针的引用,并成为孤立对象

栈是由操作系统自动管理的,而不是Go本身。因此,我们不必担心栈。另一方面,堆并不是由操作系统自动管理的,并且由于其具有最大的内存空间并保存动态数据,因此它可能会成倍增长,从而导致我们的程序随着时间耗尽内存。随着时间的流逝,它也变得支离破碎,使应用程序变慢。解决这些问题是垃圾收集的初衷。

内存管理

Go的内存管理包括在需要内存时自动分配内存,在不再需要内存时进行垃圾回收。

内存分配

Go根据对象的大小决定对象的分配过程,分为三类:

微小对象(Tiny)(size <16B):使用mcache的微小分配器分配大小小于16个字节的对象。这是高效的,并且在单个16字节块上可完成多个微小分配。

image

小对象(尺寸16B〜32KB):大小在16个字节和32k字节之间的对象被分配在G运行所在的P的mcache的对应的mspan size class上。

image
在微小型和小型对象分配中,如果mspan的列表为空,分配器将从mheap获取大量的页面用于mspan。如果mheap为空或没有足够大的页面满足分配请求,那么它将从操作系统中分配一组新的页(至少1MB)

大对象(大小> 32KB):大于32 KB的对象直接分配在mheap的相应大小类上(size class)。如果mheap为空或没有足够大的页面满足分配请求,则它将从操作系统中分配一组新的页(至少1MB)。

image

垃圾回收(GC)

Go通过垃圾回收机制管理堆内存。简单来说,它释放了孤儿对象(orphan object)使用的内存,所谓孤儿对象是指那些不再被栈直接或间接(通过另一个对象中的引用)引用的对象,从而为创建新对象的分配腾出了空间。

Go 1.12版本开始,Go使用了非分代的、并发的、基于三色标记和清除的垃圾回收器。

当完成一定百分比(GC百分比)的堆分配,GC过程就开始了。收集器将在不同工作阶段执行不同的工作:

参考

可视化讲解 Go 内存管理

《Visualizing memory management in Golang》

上一篇下一篇

猜你喜欢

热点阅读